From 833ecf197430187c53e24c62130ee66e22ae039e Mon Sep 17 00:00:00 2001 From: JoeJi Date: Wed, 14 Dec 2022 10:53:28 +1300 Subject: [PATCH 001/334] Increase build version and code release/v4.1.0 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index e2164e185d7..44ee013f6ca 100644 --- a/build.gradle +++ b/build.gradle @@ -67,8 +67,8 @@ task clean(type: Delete) { // Define versions in a single place ext { // App - appVersion = "7.0" - versionCode = 489 + appVersion = "7.1" + versionCode = 490 // Sdk and tools compileSdkVersion = 33 From 35a0c87649ee18b305e27fb0816f28275af1a1a5 Mon Sep 17 00:00:00 2001 From: JoeJi Date: Wed, 14 Dec 2022 10:54:12 +1300 Subject: [PATCH 002/334] Switch release sdk, mega sdk v4.10.0 , megachat sdk v4.1.0 release/v4.1.0 --- sdk/src/main/jni/mega/sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index fbf86343a3f..5d7468b3683 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit fbf86343a3fd60ee457d71c3caed0c9268eb9aa7 +Subproject commit 5d7468b3683fc83eddb3d182324f4859dc980e85 From fcbb29507419daa7eb9fe6facad7956a17b8ea1f Mon Sep 17 00:00:00 2001 From: JoeJi Date: Wed, 14 Dec 2022 13:49:31 +1300 Subject: [PATCH 003/334] Switch megachat sdk v4.1.0 release/v4.1.0 --- sdk/src/main/jni/megachat/sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/jni/megachat/sdk b/sdk/src/main/jni/megachat/sdk index 3a834dbfbc0..85a8a1d5c84 160000 --- a/sdk/src/main/jni/megachat/sdk +++ b/sdk/src/main/jni/megachat/sdk @@ -1 +1 @@ -Subproject commit 3a834dbfbc063f24f69a91dfd118d3ad0ea1d8a7 +Subproject commit 85a8a1d5c849178c42d0db27034994933ecb80bf From 7af56fb5648ca58a6925fabc47d46702b6beb487 Mon Sep 17 00:00:00 2001 From: JoeJi Date: Wed, 14 Dec 2022 13:55:27 +1300 Subject: [PATCH 004/334] change prebuild megaSdkVersion release/v4.1.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 44ee013f6ca..75b7bae6aef 100644 --- a/build.gradle +++ b/build.gradle @@ -77,7 +77,7 @@ ext { buildToolsVerion = '31.0.0' // Prebuilt MEGA SDK version - megaSdkVersion = '20221213.092045-rel' + megaSdkVersion = '20221214.004202-rel' // App dependencies accompanistLayoutVersion = '0.24.13-rc' From e7fa45d09e7aa8fadbbef42ade218106e64a2a4a Mon Sep 17 00:00:00 2001 From: JoeJi Date: Wed, 14 Dec 2022 14:10:45 +1300 Subject: [PATCH 005/334] Update transifex and upload string release/v4.1.0 --- app/src/main/res/values-ar/strings.xml | 4 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 10 ++-- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-id/strings.xml | 54 +++++++++++----------- app/src/main/res/values-it/strings.xml | 18 ++++---- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-ko/strings.xml | 4 +- app/src/main/res/values-nl/strings.xml | 44 +++++++++--------- app/src/main/res/values-pl/strings.xml | 34 +++++++------- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values-ro/strings.xml | 30 ++++++------ app/src/main/res/values-ru/strings.xml | 32 ++++++------- app/src/main/res/values-th/strings.xml | 2 +- app/src/main/res/values-vi/strings.xml | 14 +++--- app/src/main/res/values-zh-rCN/strings.xml | 4 +- app/src/main/res/values-zh-rTW/strings.xml | 2 +- app/src/main/res/values/strings.xml | 28 +++++------ 18 files changed, 144 insertions(+), 144 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 71c5face81a..3fdd602f048 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -4570,7 +4570,7 @@ Verify %s - Your credentials + بيانات الاعتماد الخاصة بك - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7089a09f048..af6c5382916 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -4132,5 +4132,5 @@ Ihre Identitätsdaten - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index bde0d6d2ce2..4afee8e94ad 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -655,9 +655,9 @@ Como subir - Activar subida multimedia secundaria + Activar Subida multimedia secundaria - Desactivar subida multimedia secundaria + Desactivar Subida multimedia secundaria Elige carpeta @@ -1479,7 +1479,7 @@ ¿Confirmas que quieres mover esta carpeta a la Papelera? Se desactivarán las Subidas de la cámara. - ¿Confirmas que quieres mover esta carpeta a la Papelera? Se desactivará la subida multimedia secundaria. + ¿Confirmas que quieres mover esta carpeta a la Papelera? Se desactivará la Subida multimedia secundaria. ¿Mover elemento a la Papelera? @@ -4082,7 +4082,7 @@ [B]No hay [/B][A]backups[/A] - Aquí es donde se almacenan los backups de tus archivos y carpetas. Estos backups son de “solo lectura” para protegerlos de modificaciones accidentales en tu nube.\nPuedes crear backups en MEGA desde tu pc usando nuestra aplicación de escritorio. + Aquí es donde se almacenan los backups de tus archivos y carpetas. Estos backups son de “solo lectura” para protegerlos de modificaciones accidentales en tu Nube.\nPuedes crear backups en MEGA desde tu pc usando nuestra aplicación de escritorio. Cuenta suspendida por violar los derechos de autor. Te hemos enviado un correo electrónico con más información al respecto. @@ -4242,5 +4242,5 @@ Tus credenciales - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 45befcbc00f..e4bc6d21336 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -4242,5 +4242,5 @@ Votre numéro d’identification - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index a3e75b17bd3..a155b5a0669 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -171,9 +171,9 @@ Nama - Nama Depan + Nama depan - Nama Belakang + Nama belakang Saya setuju dengan [A]Persyaratan Layanan[/A] MEGA @@ -856,7 +856,7 @@ Kunci yang anda berikan tidak sesuai dengan akun ini. Pastikan anda menggunakan Kunci pemulihan yang benar dan coba lagi. - Kunci pemulihan Salah + Kunci pemulihan salah Mendapatkan info… @@ -1271,11 +1271,11 @@ Import - Tambah ke Cloud Drive + Tambah ke Cloud drive Lihat kontak - Kesalahan. Tidak ditambahkan ke Cloud Drive + Kesalahan. Tidak ditambahkan ke Cloud drive Menghubungkan… @@ -1343,7 +1343,7 @@ Tidak ada file dipilih - Dari Cloud Drive + Dari Cloud drive Kontak @@ -1557,7 +1557,7 @@ Hapus kode QR - Ke Cloud Drive + Ke Cloud drive Ke sistem file @@ -1619,7 +1619,7 @@ [B]Kosongkan [/B][A]Sampah[/A] - [B]Tidak ada file di [/B][A]Cloud Drive[/A] anda + [B]Tidak ada file di [/B][A]Cloud drive[/A] anda [B]Tidak ada file [/B][A]Disimpan untuk Offline[/A] @@ -1882,11 +1882,11 @@ Mulai Setup - Pindai atau salin benih ke Aplikasi Authenticator anda. + Pindai atau salin benih ke aplikasi otentikasi anda. Pastikan untuk mencadangkan benih ini ke tempat yang aman jika anda kehilangan perangkat anda. - Masukkan kode 6-digit yang dihasilkan oleh aplikasi Otentikasi anda. + Masukkan kode 6-digit yang dihasilkan oleh aplikasi otentikasi anda. Verifikasi @@ -1896,7 +1896,7 @@ Otentikasi dua faktor diaktifkan - Lain kali anda masuk ke akun anda, anda akan diminta memasukkan kode 6-digit yang disediakan oleh Aplikasi Otentikasi anda. + Lain kali anda masuk ke akun anda, anda akan diminta memasukkan kode 6-digit yang disediakan oleh aplikasi otentikasi anda. Tolong simpanKunci pemulihan anda di lokasi yang aman, untuk menghindari masalah jika anda kehilangan akses ke aplikasi anda, atau jika anda ingin menonaktifkan otentikasi dua faktor. @@ -1932,11 +1932,11 @@ Aplikasi otentikasi dua faktor - Apakah anda ingin membuka Google Play agar dapat memasang Aplikasi Authenticator? + Apakah anda ingin membuka Google Play agar dapat memasang aplikasi otentikasi? Play Store - Anda memerlukan aplikasi authenticator untuk mengaktifkan 2FA di MEGA. Anda dapat mengunduh dan memasang aplikasi Google Authenticator, Duo Mobile, Authy, atau Microsoft Authenticator untuk ponsel atau tablet anda. + Anda memerlukan aplikasi otentikasi untuk mengaktifkan 2FA di MEGA. Anda dapat mengunduh dan memasang aplikasi Google Authenticator, Duo Mobile, Authy, atau Microsoft Authenticator untuk ponsel atau tablet anda. %d files tidak dibagikan @@ -2072,17 +2072,17 @@ Kode salah - Cloud Drive hampir penuh. Tingkatkan ke Pro dan dapatkan hingga %1$s penyimpanan dan %2$s kuota transfer. + Cloud drive hampir penuh. Tingkatkan ke Pro dan dapatkan hingga %1$s penyimpanan dan %2$s kuota transfer. - Cloud Drive hampir penuh. Tingkatkan sekarang dan dapatkan penyimpanan hingga %1$s dan kuota transfer %2$s. + Cloud drive hampir penuh. Tingkatkan sekarang dan dapatkan penyimpanan hingga %1$s dan kuota transfer %2$s. - Cloud Drive hampir penuh. Jika anda membutuhkan lebih banyak penyimpanan, silakan hubungi dukungan MEGA untuk mendapatkan paket khusus. + Cloud drive hampir penuh. Jika anda membutuhkan lebih banyak penyimpanan, silakan hubungi dukungan MEGA untuk mendapatkan paket khusus. - Cloud Drive penuh. Tingkatkan ke Pro dan dapatkan hingga %1$s penyimpanan dan %2$s kuota transfer. + Cloud drive penuh. Tingkatkan ke Pro dan dapatkan hingga %1$s penyimpanan dan %2$s kuota transfer. - Cloud Drive penuh. Tingkatkan sekarang dan dapatkan penyimpanan hingga %1$s dan kuota transfer %2$s. + Cloud drive penuh. Tingkatkan sekarang dan dapatkan penyimpanan hingga %1$s dan kuota transfer %2$s. - Cloud Drive penuh. Jika anda membutuhkan lebih banyak penyimpanan, silakan hubungi dukungan MEGA untuk mendapatkan paket khusus. + Cloud drive penuh. Jika anda membutuhkan lebih banyak penyimpanan, silakan hubungi dukungan MEGA untuk mendapatkan paket khusus. Lihat paket @@ -2532,7 +2532,7 @@ Obrolan terenkripsi - Obrolan yang sepenuhnya terenkripsi dengan panggilan suara dan video, perpesanan grup, dan integrasi berbagi file dengan Cloud Drive anda. + Obrolan yang sepenuhnya terenkripsi dengan panggilan suara dan video, perpesanan grup, dan integrasi berbagi file dengan Cloud drive anda. Buat jaringan anda @@ -2746,9 +2746,9 @@ Kata sandi anda bocor dan sekarang digunakan oleh aktor jahat untuk masuk ke akun anda, termasuk, namun tidak terbatas pada, akun MEGA anda. - Setel Nama Panggilan + Setel nama panggilan - Atur Nama Panggilan + Atur nama panggilan Nama panggilan ditambahkan @@ -2800,7 +2800,7 @@ Folder tidak ada. - File tidak dapat ditemukan di Cloud Drive anda. + File tidak dapat ditemukan di Cloud drive anda. Penyimpanan Penuh @@ -2834,11 +2834,11 @@ Terverifikasi - Kredensial Autentikasi + Kredensial autentikasi Ini dapat dilakukan pada kehidupan nyata melalui bertemu langsung. Jika anda memiliki channel lain yang sudah terverifikasi seperti OTR atau PGP, anda mungkin mau menggunakan itu. - Kredensial Anda + Kredensial anda Setel Ulang @@ -4021,7 +4021,7 @@ Verify %s - Your credentials + Kredensial anda - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 2e0fe1723c3..0c7eb058450 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -2026,11 +2026,11 @@ App autenticazione a due fattori - Vuoi aprire il Google Play Store in modo da installare un\’app per l\’autenticazione? + Vuoi aprire il Google Play Store in modo da installare un\’app di autenticazione? Play Store - Hai bisogno di un’applicazione di autenticazione per attivare l’Autenticazione a 2 fattori su MEGA. Puoi scaricare ed installare le app di Google Authenticator, Duo Mobile, Authy o Microsoft Authenticator per il tuo telefono o tablet. + Hai bisogno di un’applicazione di autenticazione per attivare l’autenticazione a 2 fattori su MEGA. Puoi scaricare ed installare le app di Google Authenticator, Duo Mobile, Authy o Microsoft Authenticator per il tuo telefono o tablet. %d file non sono stati condivisi @@ -2892,15 +2892,15 @@ La tua password è stata rubata e adesso viene usata da persone non identificate per entrare nei tuoi account, incluso, ma non limitato, il tuo account MEGA. - Imposta un nickname + Imposta un soprannome - Modifica nickname + Modifica il soprannome - Nickname aggiunto + Soprannome aggiunto - Nickname rimosso + Soprannome rimosso - Nickname + Soprannome Numero di telefono @@ -2986,7 +2986,7 @@ Verificato - Credenziali di Autenticità + Credenziali di autenticità Sarebbe meglio farlo faccia a faccia nella vita reale. Se hai un altro canale di comunicazione verificato come OTR o PGP, puoi utilizzarlo. @@ -4242,5 +4242,5 @@ Le tue credenziali - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 0e94e67bded..d5d13fe7fff 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -4022,5 +4022,5 @@ あなたの資格情報 - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 98a845baf1e..036bd9a7045 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -4020,7 +4020,7 @@ Verify %s - Your credentials + 당신의 자격 증명 - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index ac281c29b12..5580dec63dc 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -204,7 +204,7 @@ De geselecteerde overdrachten worden geannuleerd. - Cloud Schijf + Cloud schijf Recent @@ -242,7 +242,7 @@ Binnenkomende deling met - Geen bestanden in uw Cloud Schijf + Geen bestanden in uw Cloud schijf Lege Map @@ -1302,11 +1302,11 @@ Importeren - Toevoegen aan Cloud Schijf + Toevoegen aan Cloud schijf Contacten weergeven - Fout. Niet toegevoegd aan de Cloud Schijf + Fout. Niet toegevoegd aan de Cloud schijf Verbinden… @@ -1351,7 +1351,7 @@ Gebruik mobiele verbinding - Video’s Uploaden + Video’s uploaden Profielfoto bijgewerkt @@ -1377,7 +1377,7 @@ Geen bestanden geselecteerd - Van Cloud Schijf + Van Cloud schijf Contact @@ -1566,7 +1566,7 @@ Gebruikers account - Opslaan naar mijn \nCloud Schijf + Opslaan naar mijn \nCloud schijf De chat bestaat al @@ -1594,7 +1594,7 @@ Verwijder QR code - Naar Cloud Schijf + Naar Cloud schijf Naar bestandssysteem @@ -1656,7 +1656,7 @@ [A]Prullenbak[/A][B]Leegmaken[/B] - [B]Geen bestanden in uw [/B][A]Cloud Schijf[/A] + [B]Geen bestanden in uw [/B][A]Cloud schijf[/A] [B]Geen bestanden[/B][A]Opgeslagen voor Offline[/A] @@ -1929,11 +1929,11 @@ Begin het Instellen - Scan of kopieer de seed naar uw Authenticator Applicatie. + Scan of kopieer de seed naar uw authenticatie applicatie. Zorg ervoor dat u een backup maakt op een veilige plek voor het geval u uw apparaat verliest. - Voer de 6-cijferige code gegenereerd door uw Authenticatie Applicatie in. + Voer de 6-cijferige code gegenereerd door uw authenticatie applicatie in. Verifiëren @@ -1943,7 +1943,7 @@ Twee-staps authenticatie ingeschakeld - De volgende keer dat u inlogt in uw account wordt u gevraagd een 6-cijferige code in te voeren die gegeven is door uw Authenticatie Applicatie. + De volgende keer dat u inlogt in uw account wordt u gevraagd een 6-cijferige code in te voeren die gegeven is door uw authenticatie applicatie. Bewaar uw Herstelsleutel op een veilige locatie om problemen te voorkomen als u de toegang tot uw applicatie verliest of als u twee-staps authenticatie wilt uitschakelen. @@ -1979,7 +1979,7 @@ Twee-staps authenticatie applicatie - Wilt u Google Play openen zodat u een Authenticatie Applicatie kunt installeren? + Wilt u Google Play openen zodat u een authenticatie applicatie kunt installeren? Play Store @@ -2125,13 +2125,13 @@ Cloud Drive is bijna vol. Upgrade nu en krijg tot %1$s opslag en %2$s overdrachtstegoed. - Cloud Schijf is bijna vol. Als u meer ruimte nodig heeft neem contact op met MEGA ondersteuning om een aangepast plan te nemen. + Cloud schijf is bijna vol. Als u meer ruimte nodig heeft neem contact op met MEGA ondersteuning om een aangepast plan te nemen. Cloud Drive is vol. Upgrade naar Pro en ontvang tot %1$s opslag en %2$s overdrachtstegoed. - Cloud Schijf is vol. Upgrade nu en krijg tot %1$s opslag en %2$s overdrachtstegoed. + Cloud schijf is vol. Upgrade nu en krijg tot %1$s opslag en %2$s overdrachtstegoed. - Cloud Schijf is vol. Als u meer ruimte nodig heeft neem contact op met MEGA ondersteuning om een aangepast abonnement te nemen. + Cloud schijf is vol. Als u meer ruimte nodig heeft neem contact op met MEGA ondersteuning om een aangepast abonnement te nemen. Zie abonnementen @@ -2250,7 +2250,7 @@ De uitnodiging naar contact %s was al verzonden en kan gezien worden in de Verzonden Verzoeken tabblad. - Opslaan %s in Cloud Schijf… + Opslaan %s in Cloud schijf… Bestanden @@ -2600,7 +2600,7 @@ Gecodeerd gesprek - Volledig gecodeerd gesprek met spraak en video oproepen, groepsgesprekken en bestandsdeling integratie met uw Cloud Schijf. + Volledig gecodeerd gesprek met spraak en video oproepen, groepsgesprekken en bestandsdeling integratie met uw Cloud schijf. Creëer uw netwerk @@ -2874,7 +2874,7 @@ De map bestaat niet. - Het bestand kon niet gevonden worden in uw Cloud Schijf. + Het bestand kon niet gevonden worden in uw Cloud schijf. Opslagruimte Vol @@ -2914,7 +2914,7 @@ Dit kan het beste gebeuren door elkaar in het echte leven, face to face te ontmoeten. Als u een andere al geverifieerde kanaal heeft, zoals geverifieerd OTR of PGP, dan kunt u deze ook gebruiken. - Uw Gegevens + Uw gegevens Resetten @@ -3205,7 +3205,7 @@ Afspelen op de achtergrond - Automatisch een backup van uw foto\’s en video\’s maken naar uw Cloud Schijf. + Automatisch een backup van uw foto\’s en video\’s maken naar uw Cloud schijf. Alle @@ -4132,5 +4132,5 @@ Uw gegevens - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index e3f9a16d443..74531a2eaf6 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1640,7 +1640,7 @@ Konto użytkownika - Zapisz na mój\nCloud Drive + Zapisz na mój\nCloud drive Ten czat już istnieje @@ -1730,7 +1730,7 @@ [B]Pusty [/B][A]Kosz na śmieci[/A] - [B]Brak plików w twoim [/B][A]Cloud Drive[/A] + [B]Brak plików w twoim [/B][A]Cloud drive[/A] [B]Brak plików [/B][A]Zapisane dla Offline[/A] @@ -2023,11 +2023,11 @@ Rozpocznij instalację - Zeskanuj lub skopiuj materiał siewny do aplikacji Authenticator. + Zeskanuj lub skopiuj materiał siewny do aplikacji uwierzytelniającego. Pamiętaj, aby wykonać kopię zapasową tego materiału siewnego w bezpieczne miejsce na wypadek utraty urządzenia. - Wprowadź 6-cyfrowy kod wygenerowany przez aplikację Authenticator. + Wprowadź 6-cyfrowy kod wygenerowany przez aplikację uwierzytelniającego. Weryfikacja @@ -2037,7 +2037,7 @@ Uwierzytelnianie dwuetapowe włączone - Gdy następnym razem zalogujesz się na swoje konto, zostaniesz poproszony o podanie 6-cyfrowego kodu podanego przez aplikację Authenticator. + Gdy następnym razem zalogujesz się na swoje konto, zostaniesz poproszony o podanie 6-cyfrowego kodu podanego przez aplikację uwierzytelniającego. Zapisz swój klucz odzwyskiwania w bezpiecznym miejscu, aby uniknąć problemów w przypadku utraty dostępu do aplikacji, lub jeśli chcesz wyłączyć uwierzytelnianie dwuetapowe. @@ -2073,11 +2073,11 @@ Aplikacja do uwierzytelniania dwuskładnikowego - Czy chcesz otworzyć Google Play, aby zainstalować aplikację Authenticator App? + Czy chcesz otworzyć Google Play, aby zainstalować aplikację uwierzytelniającego? Play Store - Potrzebujesz aplikacji uwierzytelniającej, by włączyć 2FA na MEGA. Możesz pobrać i zainstalować aplikację Google Authenticator, Duo Mobile, Authy lub Microsoft Authenticator na swój telefon lub tablet. + Potrzebujesz aplikacji uwierzytelniającego, by włączyć 2FA na MEGA. Możesz pobrać i zainstalować aplikację Google Authenticator, Duo Mobile, Authy lub Microsoft Authenticator na swój telefon lub tablet. %d plików nieudostępniono @@ -2219,17 +2219,17 @@ Nieprawidłowy kod - Cloud Drive jest prawie pełny. Przejdź na wersję Pro i uzyskaj do %1$s miejsca na dane i %2$s przydziału transferu. + Cloud drive jest prawie pełny. Przejdź na wersję Pro i uzyskaj do %1$s miejsca na dane i %2$s przydziału transferu. - Cloud Drive jest prawie pełny. Uaktualnij teraz i uzyskaj do %1$s przestrzeni dyskowej i %2$s limitu transferu. + Cloud drive jest prawie pełny. Uaktualnij teraz i uzyskaj do %1$s przestrzeni dyskowej i %2$s limitu transferu. - Cloud Drive jest prawie pełny. Jeśli potrzebujesz więcej miejsca, skontaktuj się z pomocą techniczną MEGA, aby uzyskać niestandardowy plan. + Cloud drive jest prawie pełny. Jeśli potrzebujesz więcej miejsca, skontaktuj się z pomocą techniczną MEGA, aby uzyskać niestandardowy plan. - Cloud Drive jest pełny. Przejdź na wersję Pro i uzyskaj do %1$s miejsca na dane i %2$s przydziału transferu. + Cloud drive jest pełny. Przejdź na wersję Pro i uzyskaj do %1$s miejsca na dane i %2$s przydziału transferu. Dysk w chmurze jest pełny. Uaktualnij teraz i uzyskaj do %1$s przestrzeni dyskowej i %2$s limitu transferu. - Cloud Drive jest pełny. Jeśli potrzebujesz więcej miejsca, skontaktuj się z pomocą techniczną MEGA, aby uzyskać niestandardowy plan. + Cloud drive jest pełny. Jeśli potrzebujesz więcej miejsca, skontaktuj się z pomocą techniczną MEGA, aby uzyskać niestandardowy plan. Zobacz abonamenty @@ -2350,7 +2350,7 @@ Zaproszenie do kontaktu %s zostało wysłane wcześniej i można się z nim zapoznać na karcie Żądania wysłane. - Zapisywanie %s w Cloud Drive… + Zapisywanie %s w Cloud drive… Pliki @@ -2736,7 +2736,7 @@ Zaszyfrowany czat - W pełni zaszyfrowany czat z połączeniami głosowymi i wideo, przesyłanie wiadomości grupowych i integracja udostępniania plików z dyskiem Cloud Drive. + W pełni zaszyfrowany czat z połączeniami głosowymi i wideo, przesyłanie wiadomości grupowych i integracja udostępniania plików z dyskiem Cloud drive. Stwórz swoją sieć @@ -3022,7 +3022,7 @@ Katalog nie istnieje. - Plik nie może być znaleziony w Twoim dysku Cloud Drive. + Plik nie może być znaleziony w Twoim dysku Cloud drive. Dysk pełny @@ -4350,7 +4350,7 @@ Verify %s - Your credentials + Twoje uprawnienia - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 5c5b3f76ea8..9c5df8a18b5 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -4242,5 +4242,5 @@ Suas credenciais - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 15687da044a..266f83820f3 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -245,7 +245,7 @@ Partajări primite cu - Niciun fișier în unitatea cloud + Niciun fișier în Unitatea cloud Folder gol @@ -1333,11 +1333,11 @@ Importă - Adaugă în unitatea cloud + Adaugă în Unitatea cloud Vezi contactele - Eroare. Nu a fost adăugat în unitatea cloud + Eroare. Nu a fost adăugat în Unitatea cloud Se conectează… @@ -1411,7 +1411,7 @@ Niciun fișier selectat - Din unitatea cloud + Din Unitatea cloud Contact @@ -1603,7 +1603,7 @@ Contul utilizatorului - Salvează în \nunitatea mea cloud + Salvează în \nUnitatea mea cloud Chatul există deja @@ -1631,7 +1631,7 @@ Șterge codul QR - În unitatea cloud + În Unitatea cloud În sistemul de fișiere @@ -1693,7 +1693,7 @@ [B]Empty [/B][A]Rubbish bin[/A] - [B]Niciun fișier în [/B][A]unitatea cloud[/A] + [B]Niciun fișier în [/B][A]Unitatea cloud[/A] [B]Niciun fișier [/B][A]salvat pentru offline[/A] @@ -2300,7 +2300,7 @@ Invitația către contactul %s a fost trimisă înainte și poate fi consultată în fila Cereri trimise. - Se salvează %s în unitatea cloud… + Se salvează %s în Unitatea cloud… Fișiere @@ -2668,7 +2668,7 @@ Chat criptat - Chat complet criptat cu apeluri vocale și video, mesagerie de grup și integrare pentru partajarea de fișiere din unitatea cloud. + Chat complet criptat cu apeluri vocale și video, mesagerie de grup și integrare pentru partajarea de fișiere din Unitatea cloud. Creează-ți rețeaua @@ -3288,7 +3288,7 @@ Redare în fundal - Fă backup automat fotografiilor și videoclipurilor în unitatea cloud. + Fă backup automat fotografiilor și videoclipurilor în Unitatea cloud. Toate @@ -4050,9 +4050,9 @@ Nu acum - Fișier salvat în unitatea cloud. + Fișier salvat în Unitatea cloud. - Fișier nesalvat în unitatea cloud. Încearcă din nou. + Fișier nesalvat în Unitatea cloud. Încearcă din nou. Expunere de diapozitive @@ -4082,7 +4082,7 @@ [B]Niciun felement în [/B][A]backupuri[/A] - Aici sunt stocate fișierele și folderele pentru care s-a făcut backup. Elementele pentru care s-a făcut backup sunt de tip „numai citire” pentru a le proteja împotriva modificării accidentale în unitatea cloud.\nPoți face backupuri ale elementelor de pe calculator pe MEGA folosind aplicația noastră desktop. + Aici sunt stocate fișierele și folderele pentru care s-a făcut backup. Elementele pentru care s-a făcut backup sunt de tip „numai citire” pentru a le proteja împotriva modificării accidentale în Unitatea cloud.\nPoți face backupuri ale elementelor de pe calculator pe MEGA folosind aplicația noastră desktop. Account suspended due to copyright violations. We sent you an email with more information about this. @@ -4237,7 +4237,7 @@ Verify %s - Your credentials + Acreditările tale - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 6497b1f48d2..94266d4199d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -248,7 +248,7 @@ Входящие элементы от - В облачном диске нет файлов + В Облачном диске нет файлов Папка пуста @@ -1364,11 +1364,11 @@ Импорт - Добавить на облачный диск + Добавить на Облачный диск Просмотр контактов - Ошибка. Не добавлено на облачный диск + Ошибка. Не добавлено на Облачный диск Соединение… @@ -1445,7 +1445,7 @@ Файлы не выбраны - Из облачного диска + Из Облачного диска Контакт @@ -1640,7 +1640,7 @@ Аккаунт пользователя - Сохранить на мой \nоблачный диск + Сохранить на мой \nОблачный диск Чат уже существует @@ -1668,7 +1668,7 @@ Удалить QR-код - В облачный диск + В Облачный диск В файловую систему @@ -1730,7 +1730,7 @@ [A]Корзина [/A][B]пуста[/B] - [B]Нет файлов на [/B][A]облачном диске[/A] + [B]Нет файлов на [/B][A]Облачном диске[/A] [B]Нет файлов, [/B][A]сохранённых локально[/A] @@ -2350,7 +2350,7 @@ Приглашение контакту %s было отправлено раньше, и с ним можно ознакомиться на вкладке «Отправленные запросы». - Сохранение %s в облачном диске… + Сохранение %s в Облачном диске… Файлы @@ -2736,7 +2736,7 @@ Зашифрованный чат - Полностью зашифрованный чат с голосовыми и видеозвонками, групповой обмен сообщениями и интеграция файлов с вашим облачным диском. + Полностью зашифрованный чат с голосовыми и видеозвонками, групповой обмен сообщениями и интеграция файлов с вашим Облачным диском. Создайте свою сеть @@ -3022,7 +3022,7 @@ Папка не существует. - Файл не может быть найден в облачном диске. + Файл не может быть найден в Облачном диске. Хранилище заполнено @@ -3371,7 +3371,7 @@ Воспроизведение в фоновом режиме - Автоматически загружать фото и видео на облачный диск. + Автоматически загружать фото и видео на Облачный диск. Все @@ -4154,9 +4154,9 @@ Не сейчас - Файл сохранён в облачном диске + Файл сохранён в Облачном диске - Файл не сохранён в облачном диске. Попробуйте ещё раз. + Файл не сохранён в Облачном диске. Попробуйте ещё раз. Слайдшоу @@ -4186,7 +4186,7 @@ [B]Нет [/B][A]резервных копий[/A] - Здесь хранятся резервные копии файлов и папок. Резервные копии доступны «только для чтения», чтобы защитить их от случайного изменения на облачном диске.\nВы можете делать резервные копии элементов со своего компьютера в MEGA с помощью нашего настольного приложения. + Здесь хранятся резервные копии файлов и папок. Резервные копии доступны «только для чтения», чтобы защитить их от случайного изменения на Облачном диске.\nВы можете делать резервные копии элементов со своего компьютера в MEGA с помощью нашего настольного приложения. Аккаунт заблокирован из-за нарушения авторских прав. Мы отправили вам электронное письмо с дополнительной информацией об этом. @@ -4350,7 +4350,7 @@ Verify %s - Your credentials + Ваши учётные данные - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 2765b8b082c..b62c1f95a02 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -4022,5 +4022,5 @@ ข้อมูลประจำตัวของคุณ - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 9dfaa45b584..0098b6cb305 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1882,11 +1882,11 @@ Bắt Đầu Thiết Lập - Quét hoặc sao chép đoạn chủng tử này vào app Lập Xác Thực. + Quét hoặc sao chép đoạn chủng tử này vào app lập xác thực. Xin đảm bảo lưu dự phòng đoạn chủng tử này ở nơi an toàn để đề phòng khi mất thiết bị. - Xin nhập một đoạn mã dài 6 chữ số lấy từ App Lập Xác Thực. + Xin nhập một đoạn mã dài 6 chữ số lấy từ app lập xác thực. Xác thực @@ -1896,7 +1896,7 @@ Xác thực 2 yếu tố được bật - Vào những lần đăng nhập tiếp theo, hệ thống sẽ yêu cầu nhập một đoạn mã dài 6 chữ số lấy từ App Lập Xác Thực. + Vào những lần đăng nhập tiếp theo, hệ thống sẽ yêu cầu nhập một đoạn mã dài 6 chữ số lấy từ app lập xác thực. Xin lưu Mã Phục Hồi cho tài khoản của bạn ở nơi an toàn, để tránh trường hợp mất khả năng truy cập vào ứng dụng, hoặc khi bạn muốn tắt đi xác thực 2 yếu tố. @@ -1932,7 +1932,7 @@ App Xác thực 2 yếu tố - Bạn có muốn tới CH Google Play để tải về một App Lập Xác Thực hay không? + Bạn có muốn tới CH Google Play để tải về một app lập xác thực hay không? CH Play @@ -2834,11 +2834,11 @@ Đã xác thực - Công Nhận Chứng Thực + Công nhận chứng thực Đây là cách tốt nhất để tạo các buổi meeting người thật với nhau. Nếu bạn đã có phương thức xác minh khác như OTR hay PGP, bạn cũng có thể dùng được. - Chứng Thực của Bạn + Chứng thực của bạn Đặt lại @@ -4022,5 +4022,5 @@ Chứng thực của bạn - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a165508dd7d..f5b5d3366fb 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -4020,7 +4020,7 @@ Verify %s - Your credentials + 您的凭证 - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b6354a0fcbc..b210889fbda 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -4022,5 +4022,5 @@ 您的憑證 - + Preparing file for preview \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f60ab63737c..3aac414e972 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -173,9 +173,9 @@ Name - First Name + First name - Last Name + Last name I agree with MEGA’s [A]Terms of Service[/A] @@ -644,9 +644,9 @@ How to upload - Enable Secondary Media uploads + Enable Secondary media uploads - Disable Secondary Media uploads + Disable Secondary media uploads Choose folder @@ -1351,7 +1351,7 @@ Use cellular connection - Upload Videos + Upload videos Profile picture updated @@ -1929,11 +1929,11 @@ Begin Setup - Scan or copy the seed to your Authenticator App. + Scan or copy the seed to your authenticator app. Be sure to back up this seed to a safe place in case you lose your device. - Please enter the 6-digit code generated by your Authenticator App. + Please enter the 6-digit code generated by your authenticator app. Verify @@ -1943,7 +1943,7 @@ Two-factor authentication enabled - Next time you log in to your account you will be asked to enter a 6-digit code provided by your Authenticator App. + Next time you log in to your account you will be asked to enter a 6-digit code provided by your authenticator app. Please save your Recovery key in a safe location, to avoid issues in case you lose access to your app, or if you want to disable two-factor authentication. @@ -1979,7 +1979,7 @@ Two-factor authentication app - Would you like to open Google Play so you can install an Authenticator App? + Would you like to open Google Play so you can install an authenticator app? Play Store @@ -2125,7 +2125,7 @@ Cloud drive is almost full. Upgrade now and get up to %1$s of storage and %2$s of transfer quota. - Cloud Drive is almost full. If you need more storage please contact MEGA support to get a custom plan. + Cloud drive is almost full. If you need more storage please contact MEGA support to get a custom plan. Cloud drive is full. Upgrade to Pro and get up to %1$s of storage and %2$s of transfer quota. @@ -2819,9 +2819,9 @@ Your password leaked and is now being used by bad actors to log in to your accounts, including, but not limited to, your MEGA account. - Set Nickname + Set nickname - Edit Nickname + Edit nickname Nickname added @@ -2914,7 +2914,7 @@ This is best done in real life by meeting face to face. If you have another already-verified channel such as verified OTR or PGP, you may also use that. - Your Credentials + Your credentials Reset @@ -4130,7 +4130,7 @@ Verify %s - My Credentials + Your credentials Preparing file for preview \ No newline at end of file From e19f660cbe8e90f4a73768498e6200eba7bdfceb Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Fri, 16 Dec 2022 10:36:45 +0700 Subject: [PATCH 006/334] Fix download multiple file (cherry picked from commit c3cf82cf5eee467f4904f8e01cb6894311beee9f) --- .../privacy/android/app/DownloadService.kt | 18 +++++++++--------- .../globalmanagement/TransfersManagement.kt | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/DownloadService.kt b/app/src/main/java/mega/privacy/android/app/DownloadService.kt index 054509b36b4..277018642f8 100644 --- a/app/src/main/java/mega/privacy/android/app/DownloadService.kt +++ b/app/src/main/java/mega/privacy/android/app/DownloadService.kt @@ -32,6 +32,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import mega.privacy.android.app.MegaApplication.Companion.getInstance import mega.privacy.android.app.components.saver.AutoPlayInfo import mega.privacy.android.app.constants.BroadcastConstants.ACTION_REFRESH_CLEAR_OFFLINE_SETTING @@ -198,15 +199,13 @@ class DownloadService : Service(), MegaRequestListenerInterface { private fun initialiseService() { isForeground = false canceled = false - CoroutineScope(ioDispatcher).launch { - megaApi.addRequestListener(this@DownloadService) - initialiseWifiLock() - initialiseWakeLock() - mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager - setReceivers() - setRxSubscription() - } + mNotificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager startForeground() + megaApi.addRequestListener(this@DownloadService) + initialiseWifiLock() + initialiseWakeLock() + setReceivers() + setRxSubscription() } private fun initialiseWifiLock() { @@ -390,7 +389,8 @@ class DownloadService : Service(), MegaRequestListenerInterface { downloadByOpenWith = intent.getBooleanExtra(EXTRA_DOWNLOAD_BY_OPEN_WITH, false) type = intent.getStringExtra(Constants.EXTRA_TRANSFER_TYPE) - CoroutineScope(ioDispatcher).launch { + // we don't need to create ioDispatcher here, in already run in Background Thread by rx java setup + runBlocking { processIntent(intent) } } diff --git a/app/src/main/java/mega/privacy/android/app/globalmanagement/TransfersManagement.kt b/app/src/main/java/mega/privacy/android/app/globalmanagement/TransfersManagement.kt index ceeb9737839..9839436c9f7 100644 --- a/app/src/main/java/mega/privacy/android/app/globalmanagement/TransfersManagement.kt +++ b/app/src/main/java/mega/privacy/android/app/globalmanagement/TransfersManagement.kt @@ -52,6 +52,7 @@ import nz.mega.sdk.MegaTransfer.STAGE_TRANSFERRING_FILES import nz.mega.sdk.MegaTransfer.STATE_COMPLETED import nz.mega.sdk.MegaTransfer.STATE_PAUSED import timber.log.Timber +import java.util.Collections import javax.inject.Inject import javax.inject.Singleton @@ -197,7 +198,8 @@ class TransfersManagement @Inject constructor( private val pausedTransfers = ArrayList() - private val scanningTransfers = ArrayList() + private val scanningTransfers = + Collections.synchronizedCollection(ArrayList()) private var scanningTransfersToken: MegaCancelToken? = null var isProcessingFolders = false var isProcessingTransfers = false @@ -500,14 +502,13 @@ class TransfersManagement @Inject constructor( * * @param transfer Transfer to check. */ + @Synchronized fun checkScanningTransferOnStart(transfer: MegaTransfer) { + Timber.d("checkScanningTransferOnStart ${transfer.nodeHandle}") for (data in scanningTransfers) { if (data.isTheSameTransfer(transfer)) { data.apply { - val updatedTransfer = megaApi.getTransferByTag(transfer.tag) - if (!isFolder || updatedTransfer == null - || updatedTransfer.state == STATE_COMPLETED - ) { + if (!isFolder || transfer.state == STATE_COMPLETED) { removeProcessedScanningTransfer() } else { transferTag = transfer.tag @@ -527,13 +528,11 @@ class TransfersManagement @Inject constructor( * * @param transfer Transfer to check. */ + @Synchronized fun checkScanningTransferOnUpdate(transfer: MegaTransfer) { for (data in scanningTransfers) { if (data.isTheSameTransfer(transfer)) { - val updatedTransfer = megaApi.getTransferByTag(transfer.tag) - if (transfer.stage >= STAGE_TRANSFERRING_FILES - || updatedTransfer == null || updatedTransfer.state == STATE_COMPLETED - ) { + if (transfer.stage >= STAGE_TRANSFERRING_FILES || transfer.state == STATE_COMPLETED) { data.removeProcessedScanningTransfer() } else { data.transferStage = transfer.stage @@ -549,6 +548,7 @@ class TransfersManagement @Inject constructor( * * @param transfer Transfer to check. */ + @Synchronized fun checkScanningTransferOnFinish(transfer: MegaTransfer) { for (data in scanningTransfers) { if (data.isTheSameTransfer(transfer)) { From 660d041f3b3104cd8b131f3f469139d213ddc3da Mon Sep 17 00:00:00 2001 From: Rohit Soni Date: Fri, 16 Dec 2022 22:14:20 +1300 Subject: [PATCH 007/334] [AND-13974] Convert RubbishBinFragment to Kotlin --- .../app/presentation/rubbishbin/RubbishBinFragment.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt index 4e968d5bf24..b50e6242003 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt @@ -136,15 +136,14 @@ class RubbishBinFragment : Fragment() { if (managerViewModel.state.value.rubbishBinParentHandle == -1L || managerViewModel.state.value.rubbishBinParentHandle == megaApi.rubbishNode.handle ) { - Timber.d("Parent is the Rubbish: %s", - managerViewModel.state.value.rubbishBinParentHandle) + Timber.d("Parent is the Rubbish: ${managerViewModel.state.value.rubbishBinParentHandle}") nodes = megaApi.getChildren(megaApi.rubbishNode, sortOrderToInt(managerViewModel.getOrder())) } else { val parentNode = megaApi.getNodeByHandle(managerViewModel.state.value.rubbishBinParentHandle) parentNode?.let { - Timber.d("The parent node is: %s", parentNode.handle) + Timber.d("The parent node is: ${parentNode.handle}") nodes = megaApi.getChildren(parentNode, sortOrderToInt(managerViewModel.getOrder())) (requireActivity() as ManagerActivity).supportInvalidateOptionsMenu() } @@ -508,7 +507,7 @@ class RubbishBinFragment : Fragment() { } pos } - Timber.d("Push to stack %d position", lastFirstVisiblePosition) + Timber.d("Push to stack $lastFirstVisiblePosition position") lastPositionStack.push(lastFirstVisiblePosition) managerViewModel.setRubbishBinParentHandle(n.handle) @@ -806,7 +805,6 @@ class RubbishBinFragment : Fragment() { sortOrderToInt(managerViewModel.getOrder())) adapter?.setNodes(nodes) val lastVisiblePosition = if (!lastPositionStack.empty()) { - Timber.d("Pop of the stack ${lastPositionStack.pop()} position") lastPositionStack.pop() } else 0 From c4f352ace700c0e545a5959602e2381ebd300cb0 Mon Sep 17 00:00:00 2001 From: JoeJi Date: Mon, 19 Dec 2022 14:21:21 +1300 Subject: [PATCH 008/334] update version code and update sdk version release/v7.1 --- build.gradle | 2 +- sdk/src/main/jni/mega/sdk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 75b7bae6aef..84a2ed40a8c 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ task clean(type: Delete) { ext { // App appVersion = "7.1" - versionCode = 490 + versionCode = 491 // Sdk and tools compileSdkVersion = 33 diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index 5d7468b3683..bec2c9ac3af 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit 5d7468b3683fc83eddb3d182324f4859dc980e85 +Subproject commit bec2c9ac3afae5c9c324d0835411c727722fa9ec From f6ca93d29a058f62694cc774671e20dfe24b0046 Mon Sep 17 00:00:00 2001 From: JoeJi Date: Mon, 19 Dec 2022 14:30:26 +1300 Subject: [PATCH 009/334] update version code release/v7.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 84a2ed40a8c..71957a9143f 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ task clean(type: Delete) { ext { // App appVersion = "7.1" - versionCode = 491 + versionCode = 492 // Sdk and tools compileSdkVersion = 33 From 75f28d68376ad0fa96ebdc13b02af7f5123f7389 Mon Sep 17 00:00:00 2001 From: JoeJi Date: Mon, 19 Dec 2022 15:47:47 +1300 Subject: [PATCH 010/334] update megaSdkVersion release/v7.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 71957a9143f..53d442c5b57 100644 --- a/build.gradle +++ b/build.gradle @@ -77,7 +77,7 @@ ext { buildToolsVerion = '31.0.0' // Prebuilt MEGA SDK version - megaSdkVersion = '20221214.004202-rel' + megaSdkVersion = '20221219.021743-rel' // App dependencies accompanistLayoutVersion = '0.24.13-rc' From 253b003272ca61a302dec17b464c68bf8c85f3e9 Mon Sep 17 00:00:00 2001 From: Robin Shi Date: Thu, 22 Dec 2022 10:21:16 +1300 Subject: [PATCH 011/334] AND-15277 retain v6.22(487) in Google Play store for Android 6 devices --- jenkinsfile/android_release.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jenkinsfile/android_release.groovy b/jenkinsfile/android_release.groovy index bbc59f62de2..26a1d2c95e7 100644 --- a/jenkinsfile/android_release.groovy +++ b/jenkinsfile/android_release.groovy @@ -421,7 +421,7 @@ pipeline { filesPattern: 'archive/*-gms-release.aab', trackName: 'alpha', rolloutPercentage: '0', - additionalVersionCodes: '476,485', + additionalVersionCodes: '476,487', nativeDebugSymbolFilesPattern: "archive/${NATIVE_SYMBOL_FILE}", recentChangeList: getRecentChangeList(release_notes) } From db9fa97eb1755b169638151685b060da6d835bb7 Mon Sep 17 00:00:00 2001 From: JoeJi Date: Thu, 22 Dec 2022 15:33:36 +1300 Subject: [PATCH 012/334] update sdk release/v7.1 --- sdk/src/main/jni/mega/sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index bec2c9ac3af..aa57dabe78b 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit bec2c9ac3afae5c9c324d0835411c727722fa9ec +Subproject commit aa57dabe78bae7dd71e9e35286411ea2e988d3e0 From 670e6c0916da2f478e4ece09dee37355696742a6 Mon Sep 17 00:00:00 2001 From: JoeJi Date: Fri, 23 Dec 2022 09:52:59 +1300 Subject: [PATCH 013/334] update megaSdkVersion release/v7.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 53d442c5b57..45ef33e8fd1 100644 --- a/build.gradle +++ b/build.gradle @@ -77,7 +77,7 @@ ext { buildToolsVerion = '31.0.0' // Prebuilt MEGA SDK version - megaSdkVersion = '20221219.021743-rel' + megaSdkVersion = '20221222.032334-rel' // App dependencies accompanistLayoutVersion = '0.24.13-rc' From 1bb8ec3a1568f1e86d08e8ddd9306bee25846270 Mon Sep 17 00:00:00 2001 From: JoeJi Date: Fri, 23 Dec 2022 13:34:12 +1300 Subject: [PATCH 014/334] update megaSdkVersion release/v7.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 45ef33e8fd1..cbbf6cfe2f4 100644 --- a/build.gradle +++ b/build.gradle @@ -77,7 +77,7 @@ ext { buildToolsVerion = '31.0.0' // Prebuilt MEGA SDK version - megaSdkVersion = '20221222.032334-rel' + megaSdkVersion = '20221222.234343-rel' // App dependencies accompanistLayoutVersion = '0.24.13-rc' From 64a893a99e22efc72f29a23c128f5bfe94ec9dab Mon Sep 17 00:00:00 2001 From: Sida Qian Date: Fri, 23 Dec 2022 20:13:09 +1300 Subject: [PATCH 015/334] CU-140 Revert CU Upload limit --- .../android/data/facade/CameraUploadMediaFacade.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/data/src/main/java/mega/privacy/android/data/facade/CameraUploadMediaFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/CameraUploadMediaFacade.kt index 0a39ac56271..e2bf3bae4d9 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/CameraUploadMediaFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/CameraUploadMediaFacade.kt @@ -40,7 +40,7 @@ internal class CameraUploadMediaFacade @Inject constructor( isVideo: Boolean, selectionQuery: String?, ): Queue = - createMediaCursor(parentPath, selectionQuery, uri)?.let { + createMediaCursor(parentPath, selectionQuery, getPageSize(isVideo), uri)?.let { Timber.d("Extract ${it.count} Media from Cursor") extractMedia(it, parentPath) } ?: LinkedList().also { @@ -57,15 +57,18 @@ internal class CameraUploadMediaFacade @Inject constructor( context.sendBroadcast(intent) } + private fun getPageSize(isVideo: Boolean): Int = if (isVideo) 50 else 1000 + private fun createMediaCursor( parentPath: String?, selectionQuery: String?, + pageSize: Int, uri: Uri, ): Cursor? { val projection = getProjection() val mediaOrder = MediaStore.MediaColumns.DATE_MODIFIED + " ASC " return if (shouldPageCursor(parentPath)) { - mediaOrder.getPagedMediaCursor(selectionQuery, uri, projection) + mediaOrder.getPagedMediaCursor(selectionQuery, pageSize, uri, projection) } else { context.contentResolver?.query( uri, @@ -93,6 +96,7 @@ internal class CameraUploadMediaFacade @Inject constructor( private fun String.getPagedMediaCursor( selectionQuery: String?, + pageSize: Int, uri: Uri, projection: Array, ): Cursor? { @@ -101,14 +105,16 @@ internal class CameraUploadMediaFacade @Inject constructor( args.putString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER, this) args.putString(ContentResolver.QUERY_ARG_OFFSET, "0") args.putString(ContentResolver.QUERY_ARG_SQL_SELECTION, selectionQuery) + args.putString(ContentResolver.QUERY_ARG_SQL_LIMIT, pageSize.toString()) context.contentResolver?.query(uri, projection, args, null) } else { + val mediaOrderPreR = "$this LIMIT 0,$pageSize" context.contentResolver?.query( uri, projection, selectionQuery, null, - this + mediaOrderPreR ) } } From 5d1dd49ae936115d7bf6e11963c34512b2915142 Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Mon, 26 Dec 2022 10:26:32 +0800 Subject: [PATCH 016/334] AND-13974: ConvertRubbishBinFragment to Kotlin - Fix issue selected count not updating --- .../rubbishbin/RubbishBinFragment.kt | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt index b50e6242003..a8a58c5d825 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt @@ -737,19 +737,15 @@ class RubbishBinFragment : Fragment() { private fun updateActionModeTitle() { actionMode?.let { - val documents = adapter?.selectedNodes - var files = 0 - var folders = 0 - documents?.forEach { - if (it.isFile) files++ - else if (it.isFolder) folders++ + val files = adapter?.selectedNodes?.filter { it.isFile }?.size ?: 0 + val folders = adapter?.selectedNodes?.filter { it.isFolder }?.size ?: 0 + + actionMode?.title = when { + (files == 0 && folders == 0) -> 0.toString() + files == 0 -> folders.toString() + folders == 0 -> files.toString() + else -> (files + folders).toString() } - val sum = files + folders - val title = if (sum == 0) "0" - else if (files == 0) "0" - else if (folders == 0) "0" - else "$sum" - actionMode?.title = title runCatching { actionMode?.invalidate() From a5750c816b94fcb48ba90e71bcebc1a447900307 Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Mon, 26 Dec 2022 10:45:53 +0800 Subject: [PATCH 017/334] Use count instead of filter to count the number of selected items --- .../android/app/presentation/rubbishbin/RubbishBinFragment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt index a8a58c5d825..bf1f28b3ab5 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt @@ -737,8 +737,8 @@ class RubbishBinFragment : Fragment() { private fun updateActionModeTitle() { actionMode?.let { - val files = adapter?.selectedNodes?.filter { it.isFile }?.size ?: 0 - val folders = adapter?.selectedNodes?.filter { it.isFolder }?.size ?: 0 + val files = adapter?.selectedNodes?.count { it.isFile } ?: 0 + val folders = adapter?.selectedNodes?.count { it.isFolder } ?: 0 actionMode?.title = when { (files == 0 && folders == 0) -> 0.toString() From fbf67906045d61069cd8757f2e8bdf2c6ac5376f Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Mon, 26 Dec 2022 12:54:44 +0800 Subject: [PATCH 018/334] update version code --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cbbf6cfe2f4..60f6dedcdb7 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ task clean(type: Delete) { ext { // App appVersion = "7.1" - versionCode = 492 + versionCode = 493 // Sdk and tools compileSdkVersion = 33 From da37edd48e110a63d7295da445969f61e36493a7 Mon Sep 17 00:00:00 2001 From: Robin Shi Date: Wed, 28 Dec 2022 13:41:28 +1300 Subject: [PATCH 019/334] AND-15327 Support selecting SDK by tag in CD release pipeline --- jenkinsfile/android_release.groovy | 97 +++++++++++++++++++++--------- jenkinsfile/android_upload.groovy | 30 +-------- jenkinsfile/common.groovy | 54 +++++++++++++++++ 3 files changed, 122 insertions(+), 59 deletions(-) diff --git a/jenkinsfile/android_release.groovy b/jenkinsfile/android_release.groovy index 26a1d2c95e7..209fd10d4c2 100644 --- a/jenkinsfile/android_release.groovy +++ b/jenkinsfile/android_release.groovy @@ -7,6 +7,8 @@ BUILD_STEP = '' // Below values will be read from MR description and are used to decide SDK versions SDK_BRANCH = 'develop' MEGACHAT_BRANCH = 'develop' +SDK_TAG = "" +MEGACHAT_TAG = "" /** * Flag to decide whether we do clean before build SDK. @@ -31,6 +33,11 @@ ARTIFACTORY_BUILD_INFO = "buildinfo.txt" */ RELEASE_NOTES = "default_release_notes.json" +/** + * common.groovy file with common methods + */ +def common + pipeline { agent { label 'mac-jenkins-slave-android || mac-jenkins-slave' } options { @@ -106,6 +113,16 @@ pipeline { } } stages { + stage('Load Common Script') { + steps { + script { + BUILD_STEP = 'Load Common Script' + + // load the common library script + common = load('jenkinsfile/common.groovy') + } + } + } stage('Preparation') { when { expression { triggeredByDeliverAppStore() || triggeredByUploadSymbol() } @@ -137,26 +154,8 @@ pipeline { steps { script { BUILD_STEP = 'Fetch SDK Submodules' + common.fetchSdkSubmodules() } - withCredentials([gitUsernamePassword(credentialsId: 'Gitlab-Access-Token', gitToolName: 'Default')]) { - script { - sh """ - cd ${WORKSPACE} - git config --file=.gitmodules submodule.\"sdk/src/main/jni/mega/sdk\".url ${env.GITLAB_BASE_URL}/sdk/sdk.git - git config --file=.gitmodules submodule.\"sdk/src/main/jni/mega/sdk\".branch develop - git config --file=.gitmodules submodule.\"sdk/src/main/jni/megachat/sdk\".url ${env.GITLAB_BASE_URL}/megachat/MEGAchat.git - git config --file=.gitmodules submodule.\"sdk/src/main/jni/megachat/sdk\".branch develop - git submodule sync - git submodule update --init --recursive --remote - cd sdk/src/main/jni/mega/sdk - git fetch - cd ../../megachat/sdk - git fetch - cd ${WORKSPACE} - """ - } - } - } } stage('Select SDK Version') { @@ -169,13 +168,21 @@ pipeline { } withCredentials([gitUsernamePassword(credentialsId: 'Gitlab-Access-Token', gitToolName: 'Default')]) { script { - checkoutSdkByBranch(SDK_BRANCH) - checkoutMegaChatSdkByBranch(MEGACHAT_BRANCH) + if (isDefined(SDK_TAG)) { + common.checkoutSdkByTag(SDK_TAG) + } else { + common.checkoutSdkByBranch(SDK_BRANCH) + } + + if (isDefined(MEGACHAT_TAG)) { + common.checkoutMegaChatSdkByTag(MEGACHAT_TAG) + } else { + common.checkoutMegaChatSdkByBranch(MEGACHAT_BRANCH) + } } } } } - stage('Download Dependency Lib for SDK') { when { expression { triggeredByDeliverAppStore() || triggeredByUploadSymbol() } @@ -500,7 +507,7 @@ String getValueInMRDescriptionBy(String key) { * @param value value of tag * @return true if tag has a value. false if tag is null or zero length */ -static boolean isDefined(String value) { +private boolean isDefined(String value) { return value != null && !value.isEmpty() } @@ -638,8 +645,6 @@ private String getBuildVersionInfo() { String chatCommitLink = "${env.GITLAB_BASE_URL}/megachat/MEGAchat/-/commit/" + megaChatSdkCommitId() String appBranch = env.gitlabSourceBranch - String sdkBranch = SDK_BRANCH - String chatBranch = MEGACHAT_BRANCH def message = """ Version: ${readAppVersion1()}
@@ -647,8 +652,8 @@ private String getBuildVersionInfo() { - Google (GMS): [AAB](${gmsAabUrl}) | [APK](${gmsApkUrl})
Build info:
- [Android commit](${appCommitLink}) (`${appBranch}`)
- - [SDK commit](${sdkCommitLink}) (`${sdkBranch}`)
- - [Karere commit](${chatCommitLink}) (`${chatBranch}`)
+ - [SDK commit](${sdkCommitLink}) (`${sdkBranchName()}`)
+ - [Karere commit](${chatCommitLink}) (`${megaChatBranchName()}`)
""" return message } @@ -664,8 +669,8 @@ def createBriefBuildInfoFile() { Version: v${readAppVersion1()} Upload Time: ${new Date().toString()} Android: branch(${env.gitlabSourceBranch}) - commit(${appCommitId()}) -SDK: branch(${SDK_BRANCH}) - commit(${sdkCommitId()}) -Karere: branch(${MEGACHAT_BRANCH}) - commit(${megaChatSdkCommitId()}) +SDK: branch(${sdkBranchName()}) - commit(${sdkCommitId()}) +Karere: branch(${megaChatBranchName()}) - commit(${megaChatSdkCommitId()}) """ sh "rm -fv ${ARTIFACTORY_BUILD_INFO}" sh "echo \"${content}\" >> ${WORKSPACE}/${ARCHIVE_FOLDER}/${ARTIFACTORY_BUILD_INFO}" @@ -681,12 +686,16 @@ private String skipMessage(String lineBreak) { * Read SDK versions from MR description and assign the values into environment. */ private void checkSDKVersion() { + SDK_TAG = getValueInMRDescriptionBy("SDK_TAG") + MEGACHAT_TAG = getValueInMRDescriptionBy("MEGACHAT_TAG") + SDK_BRANCH = getValueInMRDescriptionBy("SDK_BRANCH") + MEGACHAT_BRANCH = getValueInMRDescriptionBy("MEGACHAT_BRANCH") + if (!isDefined(SDK_BRANCH)) { SDK_BRANCH = "develop" } - MEGACHAT_BRANCH = getValueInMRDescriptionBy("MEGACHAT_BRANCH") if (!isDefined(MEGACHAT_BRANCH)) { MEGACHAT_BRANCH = "develop" } @@ -875,4 +884,32 @@ private String releaseNotes(releaseNoteFile) { return release_notes } +/** + * Get the SDK branch name for report. If build is specified by tag + * + * @return If SDK is specified by SDK_BRANCH, return the branch name. If SDK is specified + * by SDK_TAG, return the tag name. + */ +private String sdkBranchName() { + if (isDefined(SDK_TAG)) { + return SDK_TAG + } else { + return SDK_BRANCH + } +} + +/** + * Get the MEGAChat SDK branch name for report. If build is specified by tag + * + * @return If SDK is specified by MEGACHAT_BRANCH, return the branch name. If SDK is specified + * by MEGACHAT_TAG, return the tag name. + */ +private String megaChatBranchName() { + if (isDefined(MEGACHAT_TAG)) { + return MEGACHAT_TAG + } else { + return MEGACHAT_BRANCH + } +} + diff --git a/jenkinsfile/android_upload.groovy b/jenkinsfile/android_upload.groovy index 860ccbc2d98..6628c6ba126 100644 --- a/jenkinsfile/android_upload.groovy +++ b/jenkinsfile/android_upload.groovy @@ -642,38 +642,10 @@ String getValueInMRDescriptionBy(String key) { * @param value value of tag * @return true if tag has a value. false if tag is null or zero length */ -static boolean isDefined(String value) { +private boolean isDefined(String value) { return value != null && !value.isEmpty() } -/** - * checkout SDK by git tag - * @param sdkTag the tag to checkout - */ -private void checkoutSdkByTag(String sdkTag) { - sh """ - echo checkoutSdkByTag - cd $WORKSPACE - cd sdk/src/main/jni/mega/sdk - git checkout tags/$sdkTag - cd $WORKSPACE - """ -} - -/** - * checkout MEGAchat SDK by git tag - * @param megaChatTag the tag to checkout - */ -private void checkoutMegaChatSdkByTag(String megaChatTag) { - sh """ - echo checkoutMegaChatSdkByTag - cd $WORKSPACE - cd sdk/src/main/jni/megachat/sdk - git checkout tags/$megaChatTag - cd $WORKSPACE - """ -} - /** * checkout SDK by branch * @param sdkBranch the branch to checkout diff --git a/jenkinsfile/common.groovy b/jenkinsfile/common.groovy index c8e997e7fff..69fc0109f68 100644 --- a/jenkinsfile/common.groovy +++ b/jenkinsfile/common.groovy @@ -129,4 +129,58 @@ void downloadJenkinsConsoleLog(String downloaded) { } } +/** + * checkout SDK by git tag + * @param sdkTag the tag to checkout + */ +void checkoutSdkByTag(String sdkTag) { + sh """ + echo checkoutSdkByTag + cd $WORKSPACE + cd sdk/src/main/jni/mega/sdk + git checkout tags/$sdkTag + cd $WORKSPACE + """ +} + +/** + * checkout MEGAchat SDK by git tag + * @param megaChatTag the tag to checkout + */ +void checkoutMegaChatSdkByTag(String megaChatTag) { + sh """ + echo checkoutMegaChatSdkByTag + cd $WORKSPACE + cd sdk/src/main/jni/megachat/sdk + git checkout tags/$megaChatTag + cd $WORKSPACE + """ +} + +/** + * checkout SDK by branch + * @param sdkBranch the branch to checkout + */ +private void checkoutSdkByBranch(String sdkBranch) { + sh "echo checkoutSdkByBranch" + sh "cd \"$WORKSPACE\"" + sh "git config --file=.gitmodules submodule.\"sdk/src/main/jni/mega/sdk\".url ${env.GITLAB_BASE_URL}/sdk/sdk.git" + sh "git config --file=.gitmodules submodule.\"sdk/src/main/jni/mega/sdk\".branch \"$sdkBranch\"" + sh 'git submodule sync' + sh 'git submodule update --init --recursive --remote' +} + +/** + * checkout MEGAchat SDK by branch + * @param megaChatBranch the branch to checkout + */ +private void checkoutMegaChatSdkByBranch(String megaChatBranch) { + sh "echo checkoutMegaChatSdkByBranch" + sh "cd \"$WORKSPACE\"" + sh "git config --file=.gitmodules submodule.\"sdk/src/main/jni/megachat/sdk\".url ${env.GITLAB_BASE_URL}/megachat/MEGAchat.git" + sh "git config --file=.gitmodules submodule.\"sdk/src/main/jni/megachat/sdk\".branch \"${megaChatBranch}\"" + sh 'git submodule sync' + sh 'git submodule update --init --recursive --remote' +} + return this \ No newline at end of file From 51889832174b5d3b6198a114676e24b5c4858360 Mon Sep 17 00:00:00 2001 From: JoeJi Date: Wed, 28 Dec 2022 15:14:12 +1300 Subject: [PATCH 020/334] update string release/v7.1 --- app/src/main/res/values-ar/strings.xml | 7 + app/src/main/res/values-de/strings.xml | 15 +- app/src/main/res/values-es/strings.xml | 33 ++- app/src/main/res/values-fr/strings.xml | 23 +- app/src/main/res/values-id/strings.xml | 131 ++++----- app/src/main/res/values-it/strings.xml | 91 +++--- app/src/main/res/values-ja/strings.xml | 11 +- app/src/main/res/values-ko/strings.xml | 29 +- app/src/main/res/values-nl/strings.xml | 107 ++++---- app/src/main/res/values-pl/strings.xml | 61 +++-- app/src/main/res/values-pt/strings.xml | 21 +- app/src/main/res/values-ro/strings.xml | 38 ++- app/src/main/res/values-ru/strings.xml | 33 ++- app/src/main/res/values-th/strings.xml | 11 +- app/src/main/res/values-vi/strings.xml | 259 +++++++++--------- .../main/res/values-vi/strings_sdk_errors.xml | 2 +- app/src/main/res/values-zh-rCN/strings.xml | 19 +- app/src/main/res/values-zh-rTW/strings.xml | 15 +- app/src/main/res/values/strings.xml | 65 +++-- 19 files changed, 550 insertions(+), 421 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 3fdd602f048..c54b5de0a84 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -4573,4 +4573,11 @@ بيانات الاعتماد الخاصة بك Preparing file for preview + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + الوصف \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index af6c5382916..6bba87ae8a9 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -794,9 +794,9 @@ Ändern - JA + Ja - NEIN + Nein Passwort vergessen? @@ -882,7 +882,7 @@ Mit Passwortschutz - (NUR PRO) + (nur Pro) Passwort setzen @@ -4132,5 +4132,12 @@ Ihre Identitätsdaten - Preparing file for preview + Datei wird für Vorschau vorbereitet + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + Beschreibung \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 4afee8e94ad..9467b8c0c54 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -217,7 +217,7 @@ %1$s (%2$s) - Carpetas compartidas + Elementos compartidos Papelera @@ -806,9 +806,9 @@ Cambiar - + - NO + No ¿Has olvidado tu contraseña? @@ -894,7 +894,7 @@ Activa la protección por contraseña - (SOLO PRO) + (solo Pro) Establecer contraseña @@ -1347,13 +1347,13 @@ Reenviar - Email enviado + Correo enviado Aviso de derechos de autor para todos los usuarios MEGA respeta los derechos de propiedad intelectual de terceros y exige a los usuarios de MEGA que respeten las leyes de propiedad intelectual. - Queda terminantemente prohibido utilizar el servicio en la nube de MEGA para infringir derechos de propiedad intelectual. No puedes subir, descargar, almacenar, compartir, mostrar, reproducir, distribuir, enviar por email, vincular o poner a disposición de ninguna forma archivos, datos o contenido que infrinjan derechos de propiedad intelectual de cualquier persona o entidad. + Queda terminantemente prohibido utilizar el servicio en la nube de MEGA para infringir derechos de propiedad intelectual. No puedes subir, descargar, almacenar, compartir, mostrar, reproducir, distribuir, enviar por correo electrónico, vincular o poner a disposición de ninguna forma archivos, datos o contenido que infrinjan derechos de propiedad intelectual de cualquier persona o entidad. Acepto @@ -2778,7 +2778,7 @@ Chat - [B]No hay archivos [/B][A]recientes[/A] + [B]No hay archivos [/B] en [A]Recientes[/A] %1$s y %2$d más @@ -2874,7 +2874,7 @@ Error. No se ha podido crear la carpeta %1$s - Verifica tu dirección de email + Verifica tu dirección de e-mail Tu cuenta ha sido bloqueada temporalmente por seguridad. @@ -2955,12 +2955,12 @@ ¡Tus datos están en riesgo! - Te hemos contactado por email a %1$s el %2$s pero sigues teniendo %3$s archivos que ocupan %4$s en tu cuenta de MEGA, lo cual requiere una cuenta %5$s. - Te hemos contactado por email a %1$s el %2$s y el %3$s pero sigues teniendo %4$s de archivos que ocupan %5$s en tu cuenta de MEGA, lo cual requiere una cuenta %6$s. - Te hemos contactado por email a %1$s el %2$s y el %3$s pero sigues teniendo %4$s archivos que ocupan %5$s en tu cuenta de MEGA, lo cual requiere una cuenta %6$s. + Te hemos contactado por correo electrónico a %1$s el %2$s pero sigues teniendo %3$s archivos que ocupan %4$s en tu cuenta de MEGA, lo cual requiere una cuenta %5$s. + Te hemos contactado por correo electrónico a %1$s el %2$s y el %3$s pero sigues teniendo %4$s de archivos que ocupan %5$s en tu cuenta de MEGA, lo cual requiere una cuenta %6$s. + Te hemos contactado por correo electrónico a %1$s el %2$s y el %3$s pero sigues teniendo %4$s archivos que ocupan %5$s en tu cuenta de MEGA, lo cual requiere una cuenta %6$s. - Te hemos contactado por email a %1$s pero sigues teniendo %2$s archivos que ocupan %3$s en tu cuenta de MEGA, lo cual requiere una cuenta %4$s. + Te hemos contactado por correo electrónico a %1$s pero sigues teniendo %2$s archivos que ocupan %3$s en tu cuenta de MEGA, lo cual requiere una cuenta %4$s. [B]Te quedan[M]%s[/M] para ampliar la cuenta[/B]. Después de eso, tus datos estarán sujetos a eliminación. @@ -4242,5 +4242,12 @@ Tus credenciales - Preparing file for preview + Preparando archivo para vista previa + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + Descripción \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e4bc6d21336..2321f8e1990 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -806,7 +806,7 @@ Changer - OUI + Oui Non @@ -894,7 +894,7 @@ Définir une protection par mot de passe - (PRO SEULEMENT) + (Pro seulement) Définir un mot de passe @@ -937,9 +937,9 @@ Veuillez vérifier vos courriels et toucher le lien pour confirmer votre compte. - 1 élément - %1$d éléments - %1$d éléments + 1 élément + %1$d d’éléments + %1$d éléments Le compte utilisateur associé a été résilié en raison du non-respect répété de nos Conditions générales d’utilisation. @@ -1695,7 +1695,7 @@ [B]Aucun fichier [/B][A]dans votre Disque nuagique[/A] - [B]Aucun fichier enregistré [/B][A]Hors ligne[/A] + [B]Aucun fichier[/B][A]enregistré Hors ligne[/A] [B]Aucun [/B][A]contact[/A] @@ -1876,7 +1876,7 @@ Ne plus afficher - Confirmer le mot de passe + Tester le mot de passe Le mot de passe a été accepté @@ -4242,5 +4242,12 @@ Votre numéro d’identification - Preparing file for preview + Préparation de l’aperçu du fichier + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + Description \ No newline at end of file diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index a155b5a0669..3f5679123e3 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -129,7 +129,7 @@ Masuk - Email + E-mail Kata Sandi @@ -141,9 +141,9 @@ Buat akun - Harap masukkan alamat email anda + Harap masukkan alamat e-mail anda - Alamat email anda salah + Alamat e-mail anda salah Harap masukkan password anda @@ -189,7 +189,7 @@ Kata sandi berbeda - Alamat email ini telah memiliki akun terdaftar di MEGA + Alamat e-mail ini telah memiliki akun terdaftar di MEGA Sedang menghubungi server: Membuat akun @@ -255,7 +255,7 @@ Izin - Izin Berbagi + Izin berbagi Ganti izin @@ -664,7 +664,7 @@ Folder Secondary lokal - Folder Sekunder MEGA + Folder sekunder MEGA Hanya foto @@ -772,7 +772,7 @@ Mengekspor Kunci pemulihan dan menyimpan nya di tempat yang aman membolehkan anda mengatur password baru tanpa kehilangan data. - Orang luar yang ingin merusak tidak dapat mendapatkan akses ke akun anda hanya dengan kunci anda. Ubah sandi membutuhkan kunci dan akses ke email anda. + Orang luar yang ingin merusak tidak dapat mendapatkan akses ke akun anda hanya dengan kunci anda. Ubah sandi membutuhkan kunci dan akses ke e-mail anda. Salin Kunci pemulihan ke clipboard atau simpan sebagai file teks. @@ -782,9 +782,9 @@ Ganti - YA + Ya - TIDAK + Tidak Lupa password anda? @@ -794,25 +794,25 @@ Ini adalah langkah terakhir untuk menghapus akun anda. Anda akan kehilangan semua data yang disimpan di cloud secara permanen. Silakan masukkan kata sandi anda di bawah ini. - Verifikasi email + Verifikasi e-mail - Harap cek email anda untuk melanjutkan. + Harap cek e-mail anda untuk melanjutkan. Sebuah kesalahan terjadi, harap coba kembali. Anda harus masuk untuk melakukan aksi ini. - Anda harus masuk untuk menyelesaikan perubahan email anda. Harap masuk kembali dengan alamat email anda saat ini, lalu ketuk lagi tautan konfirmasi anda. + Anda harus masuk untuk menyelesaikan perubahan e-mail anda. Harap masuk kembali dengan alamat e-mail anda saat ini, lalu ketuk lagi tautan konfirmasi anda. - Selamat, alamat email baru anda untuk akun MEGA ini adalah: %1$s + Selamat, alamat e-mail baru anda untuk akun MEGA ini adalah: %1$s Tidak benar Input tidak valid - Alamat email anda salah + Alamat e-mail anda salah - Harap cek alamat email dan coba kembali. + Harap cek alamat e-mail dan coba kembali. Ubah sandi @@ -838,15 +838,15 @@ Kembangkan cloud Anda.[A]Dapatkan peningkatan penyimpanan dan kuota transfer dengan akun Pro. - Alamat e-mail ini telah digunakan. Silakan gunakan alamat email lain. + Alamat e-mail ini telah digunakan. Silakan gunakan alamat e-mail lain. - Anda telah meminta link konfirmasi untuk alamat email tersebut. + Anda telah meminta link konfirmasi untuk alamat e-mail tersebut. - Ini adalah alamat email anda yang sudah ada. + Ini adalah alamat e-mail anda yang sudah ada. - Ini adalah langkah terakhir untuk mengganti email anda. Harap masukan password anda di bawah ini. + Ini adalah langkah terakhir untuk mengganti e-mail anda. Harap masukan password anda di bawah ini. - Ganti email + Ganti e-mail Tangkap @@ -860,7 +860,7 @@ Mendapatkan info… - Alamat email baru anda perlu di validasi. Harap cek email anda untuk melanjutkan. + Alamat e-mail baru anda perlu di validasi. Harap cek e-mail anda untuk melanjutkan. Hapus gambar profile anda? @@ -870,7 +870,7 @@ Atur proteksi password - (HANYA PRO) + (hanya Pro) Atur password @@ -888,13 +888,13 @@ Folder dibagikan dengan %1$d kontak. Hapus semua berbagi? - Email atau kata sandi tidak valid. Silakan coba lagi. + E-mail atau kata sandi tidak valid. Silakan coba lagi. Akun anda telah di tangguhkan karena penyalahgunaan Kebijakan Pelayanan. Harap kontak support@mega.nz Terlalu banyak coba masuk yang gagal, mohon tunggu selama satu jam. - Akun ini belum divalidasi. Tolong cek email anda. + Akun ini belum divalidasi. Tolong cek e-mail anda. Tautan folder tidak tersedia @@ -906,9 +906,9 @@ URL ini rusak atau cacat. Tautan yang anda coba akses tidak ada. - Menunggu konfirmasi email + Menunggu konfirmasi e-mail - Silakan periksa email anda dan ketuk tautan untuk mengonfirmasi akun anda. + Silakan periksa e-mail anda dan ketuk tautan untuk mengonfirmasi akun anda. %1$d barang @@ -1048,7 +1048,7 @@ Ubah profile - Tinggalkan Grup + Tinggalkan grup Partisipan @@ -1281,17 +1281,17 @@ %s sudah diundang. Konsultasikan permintaan anda yang tertunda. - Jika anda salah mengeja alamat email, perbaiki dan ketuk [A]Kirim Ulang[A]. + Jika anda salah mengeja alamat e-mail, perbaiki dan ketuk [A]Kirim Ulang[A]. Kirim lagi - Email terkirim + E-mail terkirim Peringatan hak cipta untuk semua pengguna MEGA menghargai hak cipta orang lain dan mengharuskan pengguna layanan MEGA cloud menuruti hukum hak cipta. - Anda dilarang menggunakan layanan MEGA cloud untuk melanggar hak cipta. Anda tidak boleh mengunggah, mengunduh, menyimpan, berbagi, menampilkan, mengalirkan, mendistribusikan, mengirim email, menautkan, mengirimkan atau menyediakan file, data, atau konten yang melanggar hak cipta atau hak kepemilikan orang lain atau entitas lainnya. + Anda dilarang menggunakan layanan MEGA cloud untuk melanggar hak cipta. Anda tidak boleh mengunggah, mengunduh, menyimpan, berbagi, menampilkan, mengalirkan, mendistribusikan, mengirim e-mail, menautkan, mengirimkan atau menyediakan file, data, atau konten yang melanggar hak cipta atau hak kepemilikan orang lain atau entitas lainnya. Setuju @@ -1457,7 +1457,7 @@ Undangan terkirim - Email salah + E-mail salah Saat anda menginstal aplikasi desktop MEGA, anda mendapatkan %1$sruang penyimpanan gratis, berlaku selama 365 hari. Aplikasi desktop MEGA tersedia untuk Windows, macOS, dan sebagian besar distro Linux. @@ -1623,17 +1623,17 @@ [B]Tidak ada file [/B][A]Disimpan untuk Offline[/A] - [B]Tidak ada [/B][A]Kontak[/A] + [B]Tidak ada [/B][A]kontak[/A] - [A]Tidak ada[/A] [B]Percakapan[/B] + [A]Tidak ada[/A] [B]percakapan[/B] - [A]Memuat[/A] [B]Percakapan…[/B] + [A]Memuat[/A] [B]percakapan…[/B] - [B]Tidak ada [/B][A]Folder Bersama Masuk[/A] + [B]Tidak ada [/B][A]folder bersama masuk[/A] - [B]Tidak ada [/B][A]Folder Keluar Bersama[/A] + [B]Tidak ada [/B][A]folder keluar bersama[/A] - [B]Tidak ada [/B][A]Tautan Umum[/A][B][/B] + [B]Tidak ada [/B][A]tautan umum[/A][B][/B] Permintaan terkirim @@ -1758,7 +1758,7 @@ Kesalahan. Sumber daya tidak lagi tersedia - Aktifkan Notifikasi + Aktifkan notifikasi Dengan cara ini, anda akan melihat pesan baru \ndi ponsel Android anda secara instan. @@ -1880,7 +1880,7 @@ Otentikasi dua faktor adalah lapisan keamanan kedua untuk akun anda. Yang berarti bahwa bahkan jika seseorang mengetahui kata sandi anda, mereka tidak dapat mengaksesnya, tanpa juga memiliki akses ke kode enam digit yang hanya anda miliki aksesnya. - Mulai Setup + Mulai setup Pindai atau salin benih ke aplikasi otentikasi anda. @@ -1908,7 +1908,7 @@ Ganti password - Ganti email + Ganti e-mail Hapus akun @@ -1982,7 +1982,7 @@ Terjadi kesalahan. Barang tidak dipulihkan. - Kirim Pesan + Kirim pesan Tindakan ini tidak dapat diselesaikan karena akan membawa anda melebihi batas penyimpanan anda saat ini. Apakah anda ingin meningkatkan akun anda? @@ -2032,7 +2032,7 @@ %1$dversi file, dengan total %2$s - Pengelolaan File + Pengelolaan file Hapus semua versi lama dari file saya @@ -2048,7 +2048,7 @@ Aktifkan atau nonaktifkan versi file untuk seluruh akun anda.\nAnda masih dapat menerima versi file dari folder bersama jika kontak anda telah mengaktifkan ini. - Ketuk, masukkan nama atau email + Ketuk, masukkan nama atau e-mail Tambahkan %s ke kontak anda? @@ -2068,7 +2068,7 @@ Harap masukan password anda untuk mengkonfirmasi akun anda - Tidak perlu menambahkan email anda sendiri. + Tidak perlu menambahkan e-mail anda sendiri. Kode salah @@ -2206,7 +2206,7 @@ Simpan ke perangkat - Upload ke MEGA + Unggah ke MEGA Pilih tujuan @@ -2250,7 +2250,7 @@ Folder ini telah menjadi subjek pencopotan. - Penghapusan Perselisihan + Penghapusan perselisihan Tidak dapat diakses karena melanggar Persyaratan Layanan kami @@ -2324,7 +2324,7 @@ %s telah dikompresi - Mengkompresi Video %1$d/%2$d + Mengkompresi video %1$d/%2$d Folder yang dipilih tidak valid @@ -2374,7 +2374,7 @@ MEGA tidak dapat membuat koneksi aman menggunakan SSL. Anda mungkin berada di jaringan Wi-Fi publik dengan persyaratan tambahan. - [B]Tidak ada [/B][A]Notifikasi[/A] + [B]Tidak ada [/B][A]notifikasi[/A] Siapkan MEGA @@ -2432,7 +2432,7 @@ Besok - [B]Tidak ada [/B][A]File Bersama[/A] + [B]Tidak ada [/B][A]file bersama[/A] Panggilan tidak dapat disambungkan karena jumlah peserta telah terlampaui. @@ -2470,7 +2470,7 @@ Pesan diteruskan - Lokasi yang Disematkan + Lokasi yang disematkan %d file tidak dikirim ke %d obrolan @@ -2550,7 +2550,7 @@ Memutar file media tidak dapat dilakukan saat ada panggilan yang sedang berlangsung. - Panggilan Berlangsung + Panggilan berlangsung Ketuk untuk bergabung dengan panggilan grup saat ini. @@ -2728,19 +2728,19 @@ Kesalahan. Folder %1$s tidak dibuat - Verifikasi alamat email anda + Verifikasi alamat e-mail anda Akun anda telah dikunci sementara untuk keselamatan anda. - Silakan ikuti langkah-langkah di [A]email verifikasi[/A]untuk membuka kunci akun anda . + Silakan ikuti langkah-langkah di [A]e-mail verifikasi[/A]untuk membuka kunci akun anda . Mengapa saya melihat ini? - Kirim ulang email + Kirim ulang e-mail - Email sudah terkirim. Harap tunggu beberapa menit sebelum mencoba lagi. + E-mail sudah terkirim. Harap tunggu beberapa menit sebelum mencoba lagi. - Akun Terkunci + Akun terkunci Ada kemungkinan bahwa anda menggunakan kata sandi yang sama untuk akun MEGA anda seperti untuk layanan lain, dan bahwa setidaknya salah satu dari layanan lain ini telah mengalami pelanggaran data. @@ -2802,15 +2802,15 @@ File tidak dapat ditemukan di Cloud drive anda. - Penyimpanan Penuh + Penyimpanan penuh Data anda berisiko! - Kami telah menghubungi anda melalui email ke %1$s di %2$s dan %3$s, tetapi anda masih memiliki %4$s file yang menggunakan %5$s di akun MEGA anda, yang mengharuskan anda memiliki %6$s. + Kami telah menghubungi anda melalui e-mail ke %1$s di %2$s dan %3$s, tetapi anda masih memiliki %4$s file yang menggunakan %5$s di akun MEGA anda, yang mengharuskan anda memiliki %6$s. - Kami telah menghubungi anda melalui email ke %1$s, tetapi anda masih memiliki %2$s file yang menggunakan %3$s di akun MEGA anda, yang mengharuskan anda memiliki %4$s. + Kami telah menghubungi anda melalui e-mail ke %1$s, tetapi anda masih memiliki %2$s file yang menggunakan %3$s di akun MEGA anda, yang mengharuskan anda memiliki %4$s. [B]Anda memiliki [M]%s[/M] tersisa untuk ditingkatkan [/B]. Setelah itu, data anda akan dihapus. @@ -3226,7 +3226,7 @@ Baru-baru ini ditambahkan - [B]Tidak ada [/B][A]Grup[/A] + [B]Tidak ada [/B][A]grup[/A] Permintaan @@ -3536,7 +3536,7 @@ Favorit - [B]Tidak ada[/B] [A]Favorit[/A] + [B]Tidak ada[/B] [A]favorit[/A] Hapus favorit @@ -3876,7 +3876,7 @@ Di sinilah file dan folder yang dicadangkan disimpan. Item yang dicadangkan “hanya-baca” untuk melindunginya agar tidak dimodifikasi secara tidak sengaja di drive cloud anda.Anda dapat mencadangkan item dari komputer anda ke MEGA menggunakan aplikasi desktop kami. - Akun ditangguhkan karena pelanggaran hak cipta. Kami mengirimi anda email dengan informasi lebih lanjut tentang ini. + Akun ditangguhkan karena pelanggaran hak cipta. Kami mengirimi anda e-mail dengan informasi lebih lanjut tentang ini. Akun dihentikan karena pelanggaran Persyaratan Layanan MEGA, seperti penyalahgunaan hak orang lain, berbagi dan mengimpor data ilegal, atau penyalahgunaan sistem. @@ -4024,4 +4024,11 @@ Kredensial anda Preparing file for preview + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + Deskripsi \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 0c7eb058450..7083b68b622 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -133,7 +133,7 @@ Entra - Email + E-mail Password @@ -145,9 +145,9 @@ Crea account - Inserisci il tuo indirizzo email + Inserisci il tuo indirizzo e-mail - Indirizzo email non valido + Indirizzo e-mail non valido Inserisci la tua password @@ -738,7 +738,7 @@ Vedi codice sorgente - ZIP Browser + ZIP browser Gratis @@ -806,9 +806,9 @@ Cambia - + - NO + No Hai dimenticato la password? @@ -818,23 +818,23 @@ Questo è l’ultimo step prima di eliminare il tuo account. Perderai in modo permanente tutti i tuoi dati salvati nel cloud. Inserisci la tua password qui sotto. - Verifica dell’email + Verifica dell’e-mail - Per favore, controlla la tua email per procedere. + Per favore, controlla la tua e-mail per procedere. È stato riscontrato un errore, per favore, riprova. Devi effettuare l’accesso per portare a termine questa azione. - Hai bisogno di effettuare il login per completare il cambiamento della tua email. Per favore, effettua nuovamente il login con il tuo indirizzo email corrente e poi tocca di nuovo sul link di conferma. + Hai bisogno di effettuare il login per completare il cambiamento della tua e-mail. Per favore, effettua nuovamente il login con il tuo indirizzo e-mail corrente e poi tocca di nuovo sul link di conferma. - Congratulazioni, il tuo nuovo indirizzo email per questo account MEGA è: %1$s + Congratulazioni, il tuo nuovo indirizzo e-mail per questo account MEGA è: %1$s Sbagliato Input non valido - Indirizzo email non valido + Indirizzo e-mail non valido Ti preghiamo di controllare l’indirizzo e-mail e riprovare. @@ -862,15 +862,15 @@ Fai crescere il tuo cloud.[A]Ottieni un aumento del tuo spazio di archiviazione e della tua banda di trasferimento con un account Pro. - Questo indirizzo email è già utilizzato. Per favore, utilizza un altro indirizzo email. + Questo indirizzo e-mail è già utilizzato. Per favore, utilizza un altro indirizzo e-mail. - Hai già richiesto un link di conferma per quell’indirizzo email. + Hai già richiesto un link di conferma per quell’indirizzo e-mail. - Questo è il tuo indirizzo email esistente. + Questo è il tuo indirizzo e-mail esistente. - Questo è l’ultimo passaggio per cambiare la tua email. Per favore, inserisci la tua password qui sotto. + Questo è l’ultimo passaggio per cambiare la tua e-mail. Per favore, inserisci la tua password qui sotto. - Cambia email + Cambia e-mail Cattura @@ -884,7 +884,7 @@ Acquisizione delle informazioni in corso… - La tua nuova email deve essere validata. Per favore, controlla la tua email per procedere. + La tua nuova e-mail deve essere validata. Per favore, controlla la tua e-mail per procedere. Cancellare l’immagine del profilo? @@ -894,7 +894,7 @@ Imposta la protezione della password - (SOLO PRO) + (solo Pro) Imposta una password @@ -914,13 +914,13 @@ La cartella è condivisa con %1$d contatti. Rimuovere tutte le condivisioni? - Email o password non valide. Per favore, riprova. + E-mail o password non valide. Per favore, riprova. Il tuo account è stato sospeso a causa di violazioni dei Termini di Servizio. Per favore, contatta support@mega.nz Troppi tentativi falliti di effettuare il login, per favore aspetta un’ora. - Il tuo account non è ancora stato validato. Per favore, controlla la tua email. + Il tuo account non è ancora stato validato. Per favore, controlla la tua e-mail. Link della cartella non disponibile @@ -932,9 +932,9 @@ Questo URL è corrotto o deformato. Il link a cui stai cercando di accedere non esiste. - In attesa della conferma dell’email + In attesa della conferma dell’e-mail - Per favore, controlla la tua email e poi tocca sul link per confermare il tuo account. + Per favore, controlla la tua e-mail e poi tocca sul link per confermare il tuo account. 1 oggetto @@ -1343,17 +1343,17 @@ %s è già stato invitato. Consulta le tue richieste in sospeso. - Se hai scritto male il tuo indirizzo email, correggilo e poi tocca su [A]Reinvia[A]. + Se hai scritto male il tuo indirizzo e-mail, correggilo e poi tocca su [A]Reinvia[A]. Reinvia - Email inviata + E-mail inviata Avviso di copyright a tutti gli utenti MEGA rispetta il copyright degli altri e ha bisogno che gli utenti di MEGA Cloud drive accettino e rispettino le leggi riguardo il copyright. - Ti è severamente proibito utilizzare il servizio cloud di MEGA per infrangere il copyright. Non potresti caricare, scaricare, archiviare, condividere, mostrare, streammare, distribuire, inviare per email, linkare, trasmettere o in qualunque altro modo rendere disponibile qualsiasi file, dato o contenuto che infrange il copyright o altri diritti di proprietà di qualsiasi persona o ente. + Ti è severamente proibito utilizzare il servizio cloud di MEGA per infrangere il copyright. Non potresti caricare, scaricare, archiviare, condividere, mostrare, streammare, distribuire, inviare per e-mail, linkare, trasmettere o in qualunque altro modo rendere disponibile qualsiasi file, dato o contenuto che infrange il copyright o altri diritti di proprietà di qualsiasi persona o ente. Accetta @@ -1525,7 +1525,7 @@ Invito spedito - L’email è errata + L’e-mail è errata Quando installi l\’app per desktop di MEGA ottieni %1$s di spazio complementare di archiviazione, valido per 365 giorni. L\’app per desktop di MEGA è disponibile per Windows, macOS e la maggior parte delle distribuzioni Linux. @@ -1699,9 +1699,9 @@ [B]Nessun[/B] [A]contatto[/A] - [A]Nessuna[/A] [B]Conversazione[/B] + [A]Nessuna[/A] [B]conversazione[/B] - [A] Caricamento delle[/A] [B]Conversazioni…[/B] + [A] Caricamento delle[/A] [B]conversazioni…[/B] [B]Nessuna[/B] [A]cartella condivisa in entrata[/A] @@ -1974,7 +1974,7 @@ L’autenticazione a due fattori è un secondo livello di sicurezza per il tuo account. Ciò significa che anche se qualcuno conosce la tua password non può accedervi, senza avere accesso anche al numero di sei cifre a cui hai accesso solo tu. - Inizia setup + Inizia la configurazione Scansiona o copia il seed della tua app di autenticazione. @@ -2002,7 +2002,7 @@ Modifica password - Cambia email + Cambia e-mail Elimina account @@ -2146,7 +2146,7 @@ Attiva o disattiva le versioni dei file per il tuo account.\nDisabilitare le versioni dei file non esclude che i tuoi contatti creino nuove versioni nelle cartelle condivise. - Tocca, inserisci nome o email + Tocca, inserisci nome o e-mail Aggiungere %s ai tuoi contatti? @@ -2166,7 +2166,7 @@ Si prega di inserire la password per confermare il tuo account - Non c’è bisogno di aggiungere il tuo indirizzo email + Non c’è bisogno di aggiungere il tuo indirizzo e-mail Codice non valido @@ -2874,17 +2874,17 @@ Errore. La cartella %1$s non è stata creata - Verifica il tuo indirizzo email + Verifica il tuo indirizzo e-mail Il tuo account è stato temporaneamente bloccato per la tua sicurezza. - Per favore, segui i passi nella [A]email di verifica[/A] per sbloccare il tuo account. + Per favore, segui i passi nella [A]e-mail di verifica[/A] per sbloccare il tuo account. Perché sto vedendo questo? - Reinvia email + Reinvia e-mail - Email già inviata. Per favore, attendi un po’ di minuti prima di riprovare nuovamente. + E-mail già inviata. Per favore, attendi un po’ di minuti prima di riprovare nuovamente. Account bloccati @@ -2955,12 +2955,12 @@ I tuoi dati sono a rischio! - Ti abbiamo contattato tramite email a %1$s il %2$s, ma hai ancora %3$s file che occupano %4$s nel tuo account MEGA, il che richede che tu abbia %5$s. - Ti abbiamo contattato tramite email a %1$s il %2$s e il %3$s, ma hai ancora %4$s file che occupano %5$s nel tuo account MEGA, il che richede che tu abbia %6$s. - Ti abbiamo contattato tramite email a %1$s il %2$s e il %3$s, ma hai ancora %4$s file che occupano %5$s nel tuo account MEGA, il che richede che tu abbia %6$s. + Ti abbiamo contattato tramite e-mail a %1$s il %2$s, ma hai ancora %3$s file che occupano %4$s nel tuo account MEGA, il che richede che tu abbia %5$s. + Ti abbiamo contattato tramite e-mail a %1$s il %2$s e il %3$s, ma hai ancora %4$s file che occupano %5$s nel tuo account MEGA, il che richede che tu abbia %6$s. + Ti abbiamo contattato tramite e-mail a %1$s il %2$s e il %3$s, ma hai ancora %4$s file che occupano %5$s nel tuo account MEGA, il che richede che tu abbia %6$s. - Ti abbiamo contattato tramite email a %1$s, ma hai ancora %2$s che occupano %3$s nel tuo account MEGA, il che richede che tu abbia %4$s. + Ti abbiamo contattato tramite e-mail a %1$s, ma hai ancora %2$s che occupano %3$s nel tuo account MEGA, il che richede che tu abbia %4$s. [B]Ti rimangono [M]%s[/M] per aggiornare[/B]. Dopodiché, i tuoi dati saranno soggetti ad eliminazione. @@ -4084,7 +4084,7 @@ Questo posto è dove vengono archiviati i backup di file e cartelle. I tuoi backup sono di “sola lettura” per proteggerli da modifiche accidentali nel tuo Cloud drive.\nPuoi effettuare il backup degli oggetti dal tuo computer su MEGA utilizzando la nostra app per desktop. - Account sospeso a causa di violazioni del copyright. Ti abbiamo inviato un\’email con maggiori informazioni al riguardo. + Account sospeso a causa di violazioni del copyright. Ti abbiamo inviato un\’e-mail con maggiori informazioni al riguardo. Account chiuso a causa di una violazione dei Termini di Servizio di MEGA, come abuso di diritti altrui, condivisione ed importazione di dati illegali, o abuso del sistema. @@ -4242,5 +4242,12 @@ Le tue credenziali - Preparing file for preview + Preparando il file per l\’anteprima + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + Descrizione \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index d5d13fe7fff..169e3cf8d19 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -870,7 +870,7 @@ パスワード保護を設定 - (PROのみ) + (Proのみ) パスワードを設定 @@ -4022,5 +4022,12 @@ あなたの資格情報 - Preparing file for preview + プレビュー用にファイルを準備しています + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + 説明 \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 036bd9a7045..f2b21e11cfe 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -3993,34 +3993,41 @@ 클립보드에 링크 복사됨 - 앨범 %d개 삭제함 + 사진첩 %d개 삭제함 - 앨범을 삭제할까요? + 사진첩을 삭제할까요? 사진첩이 삭제되지만 항목들은 연표에 남게 됩니다. - See less + 적게 보기 - Contact verification + 연락처 인증 - We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to verify the contacts you share information with before they can access the shared folders. + 우리는 당신의 데이터를 영지식 암호화로 보호합니다. 추가적인 보안을 보장하기 위해, 연락처가 공유된 폴더에 접근할 수 있게 되기 전에 당신이 정보를 공유하는 연락처를 인증할 것을 요청합니다. - We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to verify the contacts you receive information from before you can access the shared folders. + 우리는 당신의 데이터를 영지식 암호화로 보호합니다. 추가적인 보안을 보장하기 위해, 공유된 폴더에 접근하기 전에 당신이 정보를 받는 연락처를 인증할 것을 요청합니다. - To verify your contact, ensure the credentials you see above match their account credentials. You can ask them to share their credentials with you. + 연락처를 인증하려면, 위에 보이는 자격 증명이 연락처의 계정 자격 증명과 일치하는지 확인하세요. 연락처에게 자격 증명을 공유해달라고 요청하세요. - [Undecrypted folder] + [해독 되지 않은 폴더] - To access the shared folder, the person who shared it with you should verify you, too. + 공유된 폴더에 접근하려면, 당신에게 공유해준 사람도 당신을 인증해야 합니다. - Verify %s + %s님 인증 당신의 자격 증명 - Preparing file for preview + 파일 미리보기 준비 중 + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + 설명 \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 5580dec63dc..2fc2728b50b 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -258,7 +258,7 @@ Toestemmingen - Machtigingen Delen + Machtigingen delen Machtigingen wijzigen @@ -675,7 +675,7 @@ Lokale Secundaire map - MEGA Secundaire map + MEGA secundaire map Alleen foto’s @@ -726,7 +726,7 @@ Broncode weergeven - ZIP Bestanden + ZIP bestanden Gratis @@ -794,9 +794,9 @@ Wijzigen - JA + Ja - NEE + Nee Uw wachtwoord vergeten? @@ -808,13 +808,13 @@ E-mail verificatie - Controleer uw email om door te gaan. + Controleer uw e-mail om door te gaan. Er is een fout opgetreden, probeer nogmaals. U moet ingelogd zijn om deze actie uit te voeren. - U moet ingelogd zijn om uw email wijziging te voltooien. Log opnieuw in met uw huidige e-mailadres en tik dan opnieuw op uw bevestigingslink. + U moet ingelogd zijn om uw e-mail wijziging te voltooien. Log opnieuw in met uw huidige e-mailadres en tik dan opnieuw op uw bevestigingslink. Gefeliciteerd, uw nieuwe e-mailadres voor dit MEGA-account is: %1$s @@ -882,7 +882,7 @@ Wachtwoord bescherming instellen - (ALLEEN VOOR PRO) + (allen voor Pro) Wachtwoord instellen @@ -919,9 +919,9 @@ Deze URL is corrupt of misvormd. De link die u wilt bereiken bestaat niet. - In afwachting op email bevestiging + In afwachting op e-mail bevestiging - Check uw email en tik op de link om uw account te bevestigen. + Check uw e-mail en tik op de link om uw account te bevestigen. 1 item @@ -1067,7 +1067,7 @@ Profiel wijzigen - Groep Verlaten + Groep verlaten Deelnemers @@ -1316,7 +1316,7 @@ Opnieuw verzenden - Email verstuurd + E-mail verstuurd Waarschuwing over auteursrechten aan alle gebruikers @@ -1491,7 +1491,7 @@ Uitnodiging verstuurd - Email is misvormed + E-mail is misvormed Wanneer u de MEGA Desktop Applicatie installeert krijgt u %1$s aan extra opslag ruimte, geldig voor 365 dagen. De MEGA Desktop Applicatie is beschikbaar voor Windows, macOS en de meeste Linux distributies. @@ -1586,13 +1586,13 @@ Geen contact toestemmingen toegekend - Mijn QR code + Mijn QR-code - QR code + QR-code - Reset QR code + Reset QR-code - Verwijder QR code + Verwijder QR-code Naar Cloud schijf @@ -1602,17 +1602,17 @@ Code scannen - MEGA gebruikers die uw QR code scannen, worden automatisch toegevoegd aan uw lijst met contactpersonen. + MEGA gebruikers die uw QR-code scannen, worden automatisch toegevoegd aan uw lijst met contactpersonen. Link gekopieerd naar het klembord - QR code is succesvol gereset + QR-code is succesvol gereset - QR code is succesvol verwijderd + QR-code is succesvol verwijderd - QR code niet gereset vanwege een fout. Probeer het opnieuw. + QR-code niet gereset vanwege een fout. Probeer het opnieuw. - QR code niet verwijderd vanwege een fout. Probeer het opnieuw. + QR-code niet verwijderd vanwege een fout. Probeer het opnieuw. Uitnodiging verstuurd @@ -1626,11 +1626,11 @@ Er is een fout opgetreden bij het downloaden van het QR bestand. Misschien bestaat het bestand niet. Probeer het later opnieuw. - De QR Code was succesvol gedownload naar %s + De QR-code was succesvol gedownload naar %s Uitnodiging niet verzonden - De QR code of contact link is ongeldig. Probeer een geldige code te scannen of een geldige link te openen. + De QR-code of contact link is ongeldig. Probeer een geldige code te scannen of een geldige link te openen. De uitnodiging was niet verstuurd. %s is al in uw contactlijst. @@ -1638,13 +1638,13 @@ QR-code genereren… - QR code scannen + QR-code scannen Link kopiëren - QR code Creëren + QR-code Creëren - QR code succesvol gecreeerd + QR-code succesvol gecreeerd Zet de QR-code klaar om het te scannen met de camera van uw apparaat @@ -1654,23 +1654,23 @@ Delen via - [A]Prullenbak[/A][B]Leegmaken[/B] + [B]Lege[/B] [A]Prullenbak[/A] [B]Geen bestanden in uw [/B][A]Cloud schijf[/A] [B]Geen bestanden[/B][A]Opgeslagen voor Offline[/A] - [B]Geen [/B][A]Contacten[/A] + [B]Geen [/B][A]contacten[/A] - [A]Geen[/A] [B]Gesprekken[/B] + [A]Geen[/A] [B]gesprekken[/B] - [A]Gesprekken[/A] [B]Laden…[/B] + [B]Gesprekken[/B] [A]laden…[/A] - [B]Geen [/B][A]Binnenkomende Gedeelde mappen[/A] + [B]Geen [/B][A]binnenkomende gedeelde mappen[/A] - [B]Geen [/B][A]Uitgaande Gedeelde mappen[/A] + [B]Geen [/B][A]uitgaande gedeelde mappen[/A] - [B]Nee [/B][A]Openbare Links[/A][B][/B] + [B]Nee [/B][A]openbare links[/A][B][/B] Verstuurde verzoeken @@ -1801,7 +1801,7 @@ Fout. De middelen zijn niet langer beschikbaar - Notificaties Inschakelen + Notificaties inschakelen Op deze manier kunt u nieuwe berichten zien \ngelijk op uw Android telefoon. @@ -1846,7 +1846,7 @@ Opslaan op Bestandssysteem - Inkomende oproep + Binnenkomende oproep MEGA-pop-ups op de achtergrond zijn uitgeschakeld. \nTik om de instellingen te wijzigen. @@ -1939,7 +1939,7 @@ Volgende - Een fout heeft opgetreden bij het genereren van de seed of QR code, probeer nogmaals. + Een fout heeft opgetreden bij het genereren van de seed of QR-code, probeer nogmaals. Twee-staps authenticatie ingeschakeld @@ -2030,7 +2030,7 @@ Er is een fout opgetreden. Item niet hersteld. - Bericht Versturen + Bericht versturen Deze actie kon niet voltooid worden omdat het u over de huidige opslag limiet brengt. Wilt u uw account upgraden? @@ -2097,7 +2097,7 @@ Versionering inschakelen of uitschakelen voor uw gehele account. \nVersionering uischakelen zorgt er niet voor dat uw contacten nieuwe versies kunnen creëren in gedeelde mappen. - Tik, naam of email invoeren + Tik, naam of e-mail invoeren Wilt u %s tot uw contacten toevoegen? @@ -2375,7 +2375,7 @@ %s is gecomprimeerd - Video’s Comprimeren %1$d/%2$d + Video’s comprimeren %1$d/%2$d Ongeldige map geselecteerd @@ -2432,7 +2432,7 @@ MEGA kan geen beveiligde verbinding tot stand brengen via SSL. Mogelijk bevindt u zich op een openbaar WiFi-netwerk met aanvullende vereisten. - [B]Geen [/B][A]Meldingen[/A] + [B]Geen [/B][A]meldingen[/A] MEGA Instellen @@ -2493,7 +2493,7 @@ Morgen - [B]Geen[/B] [A]Gedeelde Bestanden[/A] + [B]Geen[/B] [A]gedeelde bestanden[/A] Er kan niet worden deelgenomen aan de oproep omdat het maximum aantal deelnemers is overschreden. @@ -2739,7 +2739,7 @@ Het is niet mogelijk om spraakberichten op te nemen tijdens een oproep die in behandeling is. - Inkomende oproep + Binnenkomende oproep Binnenkomend groepsoproep @@ -2809,11 +2809,11 @@ Waarom zie ik dit? - Email opnieuw verzenden + E-mail opnieuw verzenden E-mail al verzonden. Wacht een paar minuten voordat u het opnieuw probeert. - Gesloten Accounts + Gesloten accounts Het is mogelijk dat u hetzelfde wachtwoord gebruikt voor uw MEGA-account als voor andere diensten en dat ten minste één van deze andere diensten een gegevenslek heeft geleden. @@ -2876,7 +2876,7 @@ Het bestand kon niet gevonden worden in uw Cloud schijf. - Opslagruimte Vol + Opslagruimte vol Uw gegevens lopen gevaar! @@ -3321,7 +3321,7 @@ Recent Toegevoegd - [B]Geen [/B][A] Groepen[/A] + [B]Geen [/B][A] groepen[/A] Verzoeken @@ -3634,7 +3634,7 @@ Favorieten - [B]Geen[/B] [A]Favorieten[/A] + [B]Geen[/B] [A]favorieten[/A] Favorieten verwijderen @@ -3980,7 +3980,7 @@ Dit is waar uw back-upbestanden en mappen worden opgeslagen. Uw back-upitems zijn “alleen-lezen” om te voorkomen dat ze per ongeluk worden gewijzigd in uw clouddrive.\nU kunt een back-up maken van items op uw computer naar MEGA met behulp van onze desktop-applicatie. - Account is geschorst vanwege copyright overtredingen. We hebben u een email gestuurd met meer informatie hierover. + Account is geschorst vanwege copyright overtredingen. We hebben u een e-mail gestuurd met meer informatie hierover. Uw account was beëindigd door een schending van MEGA’s Algemene Voorwaarden, zoals misbruik van rechten van anderen; Het delen en/of importen van illegale gegevens; of systeem misbruik. @@ -4132,5 +4132,12 @@ Uw gegevens - Preparing file for preview + Bestand voorbereiden als voorbeeld + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + Omschrijving \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 74531a2eaf6..3cba9f09c2b 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -214,7 +214,7 @@ Niedawne - Wgrywanie plików + Przesyłanie plików Niedostępny @@ -236,7 +236,7 @@ Moje konto - Przesyłanie zdjęć z kamery + Przesyłanie z kamery Nadchodzące @@ -566,13 +566,13 @@ Przesyłanie z kamery w toku - Czy chcesz zatrzymać wysyłanie z aparatu? + Czy chcesz wstrzymać Przesyłanie z kamery? Wgrywanie plików Sprawdzanie plików do przesłania - Inicjalizowanie Przesyłania zdjęć z kamery + Inicjalizowanie przesyłania z kamery Przesyłanie z kamery zostało wyłączone. Twój katalog lokalny jest niedostępny. @@ -662,13 +662,13 @@ Importuj z urządzenia - Włącz Przesyłanie zdjęć z kamery + Włącz Przesyłanie z kamery Jak wgrywać zdjęcia - Włącz kopię zapasową zdjęc i filmów + Włącz kopię przesyłanych zdjęć i filmów - Wyłącz kopię zapasową zdjęc i filmów + Wyłącz kopię przesyłanych zdjęć i filmów Wybierz katalog @@ -691,13 +691,13 @@ Zachowaj nazwy plików tak jak na urządzeniu - Lokalny katalog zdjęć + Lokalny katalog kamery - Katalog na pliki z kamery + Katalog na pliki Przesłane z kamery Lokalny katalog zapasowy - Katalog na kopię zapasową plików z kamery + Katalog dodatkowy MEGA Tylko zdjęcia @@ -818,9 +818,9 @@ Zmień - TAK + Tak - NIE + Nie Zapomniałeś hasła? @@ -906,7 +906,7 @@ Dodaj zabezpieczenie hasłem - (TYLKO PRO) + (tylko Pro) Ustaw hasło @@ -1511,7 +1511,7 @@ Przenieść do Kosza? - Czy na pewno chcesz przenieść ten katalog do Kosza na śmieci? Spowoduje to wyłączenie Przesyłania z aparatu. + Czy na pewno chcesz przenieść ten katalog do Kosza na śmieci? Spowoduje to wyłączenie Przesyłanie z kamery. Czy na pewno chcesz przenieść ten katalog do Kosza na śmieci? Spowoduje to wyłączenie dodatkowego przesyłania multimediów. @@ -1730,19 +1730,19 @@ [B]Pusty [/B][A]Kosz na śmieci[/A] - [B]Brak plików w twoim [/B][A]Cloud drive[/A] + [B]Brak plików w twoim [/B][A]Dysku w chmurze[/A] [B]Brak plików [/B][A]Zapisane dla Offline[/A] [B]Brak [/B][A]kontaktów[/A] - [A]Brak[/A] [B]Rozmów[/B] + [A]Brak[/A] [B]rozmów[/B] - [A]Trwa ładowanie[/A] [B]Rozmowy…[/B] + [A]Trwa ładowanie[/A] [B]rozmowy…[/B] - [B]Brak plików [/B][A]Zapisane dla Offline[/A] + [B]No [/B][A]incoming shared folders[/A] - [B]Nie [/B][A]Wychodzące foldery współdzielone[/A] + [B]Nie [/B][A]wychodzące foldery współdzielone[/A] [B]Brak [/B][A]linków[/A][B][/B] @@ -1847,7 +1847,7 @@ Jakość wideo - Przesyłanie z aparatu musi mieć dostęp do zdjęć i innych multimediów w urządzeniu. Przejdź do strony ustawień i udziel pozwolenia. + Przesyłanie z kamery musi mieć dostęp do zdjęć i innych multimediów w urządzeniu. Przejdź do strony ustawień i udziel pozwolenia. Niska @@ -2274,7 +2274,7 @@ Czy na pewno chcesz przejść na serwer testowy? Twoje konto może mieć nieodwracalne problemy. - Otwarta kamera? + Otworzyć kamerę? Jeśli otworzysz kamerę, transmisja wideo zostanie wstrzymana w bieżącym połączeniu. @@ -2560,7 +2560,7 @@ Włącz kamerę - Umożliwia dostęp do aparatu w celu skanowania dokumentów, robienia zdjęć i wykonywania połączeń wideo. + Umożliwia dostęp do kamery w celu skanowania dokumentów, robienia zdjęć i wykonywania połączeń wideo. Włącz połączenia @@ -2744,7 +2744,7 @@ Twoje zdjęcia w chmurze - Przesyłanie z kamer to podstawowa funkcja każdego urządzenia mobilnego, a my jesteśmy objęci. Utwórz teraz swoje konto. + Przesyłanie z kamery to podstawowa funkcja każdego urządzenia mobilnego, a my jesteśmy objęci. Utwórz teraz swoje konto. Wprowadz swoje hasło @@ -2848,7 +2848,7 @@ Czaty - [B]Brak plików w [/B][A]Recents[/A] + [B]Brak plików w [/B][A]Niedawne[/A] %1$s i %2$d więcej @@ -2923,7 +2923,7 @@ Twoje konto jest obecnie [B]zawieszone[/B]. Możesz tylko przeglądać swoje dane. - MEGA nie ma dostępu do Państwa danych. Administrator konta biznesowego ma jednak dostęp do danych Camera Uploads. + MEGA nie ma dostępu do Państwa danych. Administrator konta biznesowego ma jednak dostęp do danych Przesyłane z kamery. Wystąpiły problemy @@ -3566,9 +3566,9 @@ Mikrofon jest włączony - Aparat + Kamera - Aparat jest włączony. + Kamera jest włączona. Kamera jest wyłączona. @@ -4353,4 +4353,11 @@ Twoje uprawnienia Preparing file for preview + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + Opis \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 9c5df8a18b5..b8581116156 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -894,7 +894,7 @@ Estabelecer uma senha de acesso - (SOMENTE PRO) + (somente Pro) Definir senha @@ -1697,15 +1697,15 @@ [B]Não há arquivos [/B][A]Offline[/A] - [B]Não há [/B][A]Contatos[/A] + [B]Não há [/B][A]contatos[/A] [A]Não há[/A] [B]chats[/B] [A]Carregando[/A] [B]chats…[/B] - [B]Não há [/B][A]Pastas compartilhadas comigo[/A] + [B]Não há [/B][A]pastas compartilhadas comigo[/A] - [B]Não há [/B][A]Pastas compartilhadas por mim[/A] + [B]Não há [/B][A]pastas compartilhadas por mim[/A] [B]Não há [/B][A]links públicos[/A][B][/B] @@ -2490,7 +2490,7 @@ Não foi possível conectar-se ao MEGA de forma segura via SSL. Você pode estar usando uma rede Wi-Fi pública com requisitos adicionais. - [B]Não há [/B][A]Notificações[/A] + [B]Não há [/B][A]notificações[/A] Configurar o MEGA @@ -3416,7 +3416,7 @@ Adicionado recentemente - [B]Não há [/B][A]Grupos[/A] + [B]Não há [/B][A]grupos[/A] Solicitações @@ -3732,7 +3732,7 @@ Favoritos - [B]Não há[/B] [A]Favoritos[/A] + [B]Não há[/B] [A]favoritos[/A] Remover favoritos @@ -4243,4 +4243,11 @@ Suas credenciais Preparing file for preview + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + Descrição \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 266f83820f3..f061d5696cc 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -375,7 +375,7 @@ Se creează folderul - Empty Rubbish bin + Golește Coșul de gunoi Ești pe cale să elimini definitiv toate elementele din Coșul de gunoi. @@ -806,9 +806,9 @@ Schimbă - DA + Da - NU + Nu Ți-ai uitat parola? @@ -894,7 +894,7 @@ Setează protecție prin parolă - (NUMAI PRO) + (numai Pro) Setează o parolă @@ -1691,7 +1691,7 @@ Partajează folosind - [B]Empty [/B][A]Rubbish bin[/A] + [A]Coș de gunoi[/A] [B]gol[/B] [B]Niciun fișier în [/B][A]Unitatea cloud[/A] @@ -4138,11 +4138,11 @@ New album - Share meeting link + Partajează linkul întâlnirii See more - Leave group + Părăsește grupul This album name is not allowed. Enter a different name. @@ -4153,26 +4153,29 @@ Add items to “%s” - Added %d item to “%s“ - Added %d items to “%s” + %d element a fost adăugat în „%s” + %d elemente au fost adăugate în „%s” + %d de elemente au fost adăugate în „%s” Informații de plată Planul pentru calitatea de membru Pro expiră în curând - Your payment for the %1$s plan was received. + Plata pentru planul %1$s a fost primită. Your payment for the %1$s plan was unsuccessful. - Your Pro membership plan will expire in 1 day. - Your Pro membership plan will expire in %1$d days. + Planul tău pentru calitatea de membru Pro va expira în 1 zi. + Planul tău pentru calitatea de membru Pro va expira în %1$d zile. + Planul tău pentru calitatea de membru Pro va expira în %1$d de zile. - Your Pro membership plan expired 1 day ago. - Your Pro membership plan expired %1$d days ago. + Planul tău pentru calitatea de membru Pro a expirat acum 1 zi. + Planul tău pentru calitatea de membru Pro a expirat acum %1$d zile. + Planul tău pentru calitatea de membru Pro a expirat acum %1$d de zile. [A]Album[/A] [B]gol[/B] @@ -4240,4 +4243,11 @@ Acreditările tale Preparing file for preview + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + Descriere \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 94266d4199d..6f4887c2313 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -818,9 +818,9 @@ Изменить - ДА + Да - НЕТ + Нет Забыли пароль? @@ -906,7 +906,7 @@ Установить защиту паролем - (ТОЛЬКО PRO) + (только Pro) Задайте пароль @@ -927,7 +927,7 @@ К папке открыт доступ %1$d контактам. Закрыть доступ всем? - Неверный e-mail или пароль. Пожалуйста, попробуйте ещё раз. + Неверная почта или пароль. Пожалуйста, попробуйте ещё раз. Ваш аккаунт был заблокирован из-за нарушений условий использования. Пожалуйста, свяжитесь с support@mega.nz @@ -1374,7 +1374,7 @@ %s уже приглашён. Посмотрите отправленные запросы. - Если вы ввели неправильный e-mail, исправьте его и нажмите [A]«Отправить повторно»[A]. + Если вы ввели неправильный адрес электронной почты, исправьте его и нажмите [A]«Отправить повторно»[A]. Выслать повторно @@ -2195,7 +2195,7 @@ Включите или отключите управление версиями файлов для всего аккаунта.\nОтключение версий файлов не запретит вашим контактам создавать новые версии в общих папках. - Коснитесь, введите имя или e-mail + Коснитесь, введите имя или эл. почту Добавить пользователя %s в контакты? @@ -2215,7 +2215,7 @@ Пожалуйста, введите пароль, чтобы подтвердить свой аккаунт - Не обязательно добавлять свой собственный email адрес + Не нужно добавлять свой собственный адрес электронной почты. Недействительный код @@ -4334,9 +4334,9 @@ Альбомы будут удалены, но их содержимое останется на «Временной шкале». - See less + Показать меньше - Contact verification + Подтверждение контакта Мы защищаем ваши данные с помощью шифрования с нулевым разглашением. Чтобы обеспечить дополнительную безопасность, мы просим вас подтвердить контакты, с которыми вы делитесь информацией, прежде чем они смогут получить доступ к общим папкам. @@ -4344,13 +4344,20 @@ Чтобы подтвердить контакт, убедитесь, что учётные данные, которые вы видите выше, соответствуют его учётным данным. Вы можете попросить его поделиться с вами своими учётными данными. - [Undecrypted folder] + [Нерасшифрованная папка] - To access the shared folder, the person who shared it with you should verify you, too. + Чтобы общая папка стала доступной, человек, который поделился ею с вами, также должен подтвердить вас. - Verify %s + Подтвердить %s Ваши учётные данные - Preparing file for preview + Подготовка файла к превью + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + Описание \ No newline at end of file diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index b62c1f95a02..384cea22102 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -870,7 +870,7 @@ ตั้งการป้องกันด้วยรหัสผ่าน - (PRO เท่านั้น) + (Pro เท่านั้น) ตั้งรหัสผ่าน @@ -4022,5 +4022,12 @@ ข้อมูลประจำตัวของคุณ - Preparing file for preview + กำลังเตรียมไฟล์เพื่อแสดงตัวอย่าง + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + รายละเอียด \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 0098b6cb305..871dd38b64f 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -59,7 +59,7 @@ Lỗi. Xin vui lòng thử lại. - MAPHUCHOI-MEGA + CHIAKHOAPHUCHOI-MEGA Không tìm thấy thư mục secondary media, xin chọn một thư mục mới @@ -97,7 +97,7 @@ Xem kiểu ảnh nhỏ - Sao lưu Mã Phục Hồi + Sao lưu Chìa Khóa Phục Hồi Hủy gói đăng ký dịch vụ @@ -129,7 +129,7 @@ Đăng nhập - Email + E-mail Mật khẩu @@ -141,9 +141,9 @@ Tạo tài khoản - Xin nhập địa chỉ email + Xin nhập địa chỉ e-mail - Địa chỉ email không phù hợp + Địa chỉ e-mail không phù hợp Xin nhập mật khẩu @@ -189,7 +189,7 @@ Hai mật khẩu này không trùng khớp - Địa chỉ email này đã đăng ký từ trước với MEGA + Địa chỉ e-mail này đã đăng ký từ trước với MEGA Đang kết nối đến máy chủ: Đang tạo tài khoản @@ -227,15 +227,15 @@ Trang Tài Khoản - Đăng Tải Camêra + Đăng tải camêra - Mục Nhận Được + Mục nhận được - Mục Gửi Đi + Mục gửI đI - Liên Kết + Liên kết - Mục Chia Sẻ Nhận Được + Mục chia sẻ nhận được Mục chia sẻ nhận được từ @@ -255,7 +255,7 @@ Quyền hạn - Quyền Hạn Được Chia Sẻ + Quyền hạn được chia sẻ Chỉnh lại quyền hạn @@ -473,11 +473,11 @@ Thêm tên liên lạc - Chìa Khóa Giải Mã + Chìa Khóa Giải Mã - Xin nhập chìa khóa mã cho đường liên kết + Xin nhập chìa khóa giải mã cho đường liên kết - Chìa khóa mã không hợp lệ + Chìa khóa giải mã không hợp lệ Âm Thanh @@ -551,9 +551,9 @@ Lưu Trữ - Khóa bằng mã số + Khóa bằng mật mã - Tùy chọn mã số + Tùy chọn mật mã Việc nén video sử dụng lượng điện năng đáng kể. Xin cắm sạc thiết bị của bạn để có thể nén videos lớn hơn %s. @@ -672,11 +672,11 @@ Ảnh và video - Khóa bằng Mã Số + Khóa bằng Mật Mã - Đổi mã số + Đổi mật mã - Đổi mã số + Đổi khóa mật mã Yêu cầu mã số @@ -685,20 +685,20 @@ Nhập - %1$d lần thử mã số bị sai + %1$d lần thử mật mã bị sai Tài khoản sẽ đăng xuất và tất cả tệp tin lưu về ngoại tuyến sẽ xóa khỏi máy sau 10 lần nhập sai mã Hai mã mới nhập không khớp. Thử lại. - Nhập mã số + Nhập mật mã của bạn - Nhập lại mã số + Nhập lại mật mã - Nhập mã số mới + Nhập mật mã mới - Nhập lại mã số mới + Nhập lại mật mã mới Thông Tin @@ -764,59 +764,59 @@ Tài Khoản Pro Lite - Sao lưu dự phòng Mã Phục Hồi + Sao lưu Chìa Khóa Phục Hồi của bạn - Mật khẩu là cần thiết để dịch Mã Phục Hồi + Mật khẩu là cần thiết để mở Chìa Khóa Phục Hồi - Dữ liệu của bạn chỉ có thể đọc được qua quá trình giải mã được dựa trên khóa mã chủ được thông dịch từ mật khẩu bạn đặt cho tài khoản. Điều này đồng nghĩa với việc nếu quên mất mật khẩu, và không lưu đề phòng Mã Phục Hồi, bạn sẽ không tài nào truy cập vào dữ liệu của mình được. + Dữ liệu của bạn chỉ có thể đọc được qua quá trình giải mã được dựa trên thuật toán mật mã được dịch từ mật khẩu bạn đặt cho tài khoản. Điều này đồng nghĩa với việc nếu quên mất mật khẩu, và không có lưu đề phòng Chìa Khóa Phục Hồi, bạn sẽ không tài nào truy cập vào dữ liệu của mình được. - Xuất ra Mã Phục Hồi và giữ mã ở một nơi an toàn và dễ nhớ sẽ giúp bạn tránh việc mất dữ liệu khi quên mật khẩu. + Xuất ra Chìa Khóa Phục Hồi và giữ chìa khóa này ở một nơi an toàn và dễ nhớ sẽ giúp bạn tránh việc mất dữ liệu khi quên mật khẩu. - Các kẻ tấn công không thể nào truy cập vào tài khoản của bạn chỉ với mã-khóa không thôi. Thủ tục đặt lại mật khẩu yêu cầu bạn phải có mã-khóa master và quyền truy cập vào email của bạn. + Các kẻ tấn công không thể nào truy cập vào tài khoản của bạn chỉ với chìa khóa không thôi. Khâu đặt lại mật khẩu yêu cầu bạn phải có chìa khóa và quyền truy cập vào email của bạn. - Sao chép Mã Phục Hồi vào bảng nhớ tạm hoặc lưu lại mã dưới dạng tệp tin chữ. + Sao chép Chìa Khóa Phục Hồi vào bảng nhớ tạm hoặc lưu lại chìa khóa dưới dạng tệp tin chữ. Lưu - Mã Phục Hồi đã được lưu lại vào bộ nhớ + Chìa Khóa Phục Hồi đã được lưu lại vào bảng nhớ Thay đổi - + - KHÔNG + Không Quên mất mật khẩu? - Điền Mã Phục Hồi ở đây + Xin điền Chìa Khóa Phục Hồi ở đây điền mật khẩu ở đây Đây là bước cuối cùng để hoàn tất việc xóa bỏ tài khoản. Bạn sẽ mất hoàn toàn tất cả dữ liệu bạn đã lưu trên ổ mây. Để tiến hành, xin nhập mật khẩu vào dưới đây. - Xác thực email + Xác thực e-mail - Xin kiểm tra hộp thư email để thục hiện bước kế tiếp. + Xin kiểm tra hộp thư e-mail để thục hiện bước kế tiếp. Phát sinh lỗi, xin thử lại sau. Cần phải đăng nhập vào tài khoản để thực hiện hoạt động này. - Bạn cần phải đăng nhập vào trước khi bạn được phép tiến hành việc thay đổi email. Xin sử dụng email hiện thời của tài khoản này và sau đó chạm vào đường liên kết để xác nhận. + Bạn cần phải đăng nhập vào trước khi bạn được phép tiến hành việc thay đổi e-mail. Xin sử dụng e-mail hiện thời của tài khoản này và sau đó chạm vào đường liên kết để xác nhận. - Địa chỉ email cho tài khoản đã được thay đổi thành công, và là: %1$s + Địa chỉ e-mail cho tài khoản đã được thay đổi thành công, và là: %1$s Xin nhập mật khẩu Nhập liệu không hợp lệ - Địa chỉ email không phù hợp + Địa chỉ e-mail không phù hợp - Xin vui lòng kiểm tra địa chỉ email và thử lại. + Xin vui lòng kiểm tra địa chỉ e-mail và thử lại. Đặt lại mật khẩu - Xin nhập Mã Phục Hồi ở dưới đây + Xin nhập Chìa Khóa Phục Hồi ở dưới đây Bạn vừa đổi mật khẩu. @@ -830,7 +830,7 @@ Liên kết phục hồi này đã bị hết hạn, xin thử lại. - Mã Phục Hồi là cách duy nhất để đặt lại mật khẩu. Xin nhập mật khẩu ở dưới đây. + Chìa Khóa Phục Hồi của bạn là cách duy nhất để đặt lại mật khẩu. Xin nhập mật khẩu ở dưới đây. Nâng Cấp @@ -838,15 +838,15 @@ Tăng cường dung lượng của ổ mây.[A]Lấy thêm không gian lưu trữ và băng thông truyền tải với tài khoản hạng Pro. - Địa chỉ email này đã được sử dụng rồi. Xin dùng địa chỉ email khác. + Địa chỉ e-mail này đã được sử dụng rồi. Xin dùng địa chỉ e-mail khác. - Bạn đã yêu cầu gửi một đường liên kết xác thực cho địa chỉ email này rồi. + Bạn đã yêu cầu gửi một đường liên kết xác thực cho địa chỉ e-mail này rồi. - Đây là địa chỉ email hiện tại của bạn. + Đây là địa chỉ e-mail hiện tại của bạn. - Đây là bước cuối cùng để hoàn tất việc thay đổi địa chỉ email. Xin vui lòng nhập mật khẩu ở dưới. + Đây là bước cuối cùng để hoàn tất việc thay đổi địa chỉ e-mail. Xin vui lòng nhập mật khẩu ở dưới. - Đổi email + Đổi e-mail Chụp ảnh @@ -854,13 +854,13 @@ Xóa ảnh đại diện - Mã vừa nhập không phù hợp với tài khoản này. Xin kiểm tra lại cho chắc chắn là Mã Phục Hồi chính xác và thử lại. + Chìa khóa bạn vừa nhập không phù hợp với tài khoản này. Xin kiểm tra lại cho chắc chắn là Chìa Khóa Phục Hồi chính xác và thử lại. - Sai Mã Phục Hồi + Chìa Khóa Phục Hồi không hợp lệ Đang lấy thông tin… - Địa chỉ email của bạn cần phải được xác minh. Vui lòng kiểm tra hộp thư email để thực hiện bước kế tiếp. + Địa chỉ e-mail của bạn cần phải được xác minh. Vui lòng kiểm tra hộp thư e-mail để thực hiện bước kế tiếp. Xóa ảnh đại diện? @@ -870,7 +870,7 @@ Đặt bảo vệ bằng mật khẩu - (CHỈ CHO HẠNG PRO) + (Chỉ cho hạng Pro) Đặt mật khẩu @@ -888,13 +888,13 @@ Thư mục này đang được chia sẻ cho %1$d tên liên lạc. Có chắc muốn xóa không? - Email hoặc mật khẩu không hợp lệ. Xin thử lại + E-mail hoặc mật khẩu không hợp lệ. Xin thử lại Tài khoản bị đình chỉ do vi phạm Điều Khoản Dịch Vụ. Xin liên hệ support@mega.nz để được giải quyết. Đăng nhập sai quá nhiều lần, xin chờ một tiếng đồng hồ rồi thử lại. - Tài khoản này chưa được kích hoạt. Xin vui lòng kiểm tra hộp thư email. + Tài khoản này chưa được kích hoạt. Xin vui lòng kiểm tra hộp thư e-mail. Liên kết thư mục bất khả dụng @@ -906,9 +906,9 @@ Đường liên kết URL vừa nhập không đúng hoặc bị viết sai. Không thể sử dụng để truy cập được. - Đang chờ xác nhận địa chỉ email + Đang chờ xác nhận địa chỉ e-mail - Vui lòng kiểm tra hộp thư email và chạm vào đường liên kết để xác nhận tài khoản của bạn. + Vui lòng kiểm tra hộp thư e-mail và chạm vào đường liên kết để xác nhận tài khoản của bạn. %1$d mục @@ -916,7 +916,7 @@ Tài khoản của chủ đường liên kết thư mục hay tệp tin này đã bị ngưng hoạt động do vi phạm Điều Khoản Dịch Vụ. - Chìa khóa mã vừa cung cấp không đúng để dùng cho thư mục này. + Chìa khóa giải mã vừa cung cấp không đúng để dùng cho đường liên kết thư mục này. Chát @@ -1048,7 +1048,7 @@ Sửa trang cá nhân - Rời Nhóm + Rời nhóm Các Thành Viên @@ -1281,17 +1281,17 @@ %s đã được mời rồi. Xem lại các lời yêu cầu đang chờ. - Nếu địa chỉ email bị viết sai, sửa lại cho đúng và chạm vào [A]Gửi lại[A]. + Nếu địa chỉ e-mail bị viết sai, sửa lại cho đúng và chạm vào [A]Gửi lại[A]. Gửi lại - Email đã được gửi + E-mail đã được gửi Thông báo mới về luật bản quyền MEGA tôn trọng luật bản quyền của người khác và yêu cầu người sử dụng dịch vụ Ổ Mây MEGA nên thực hiện theo quy định của pháp luật về quyền tác giả. - Tất cả các người sử dụng đều bị giám sát chặt chẽ khi dùng dịch vụ Ổ Mây MEGA nhằm để tránh việc lưu trữ và phân phát nội dung vi phạm bản quyền. Bạn (khách hàng hay người sử dụng) không được phép đăng tải, tải xuống, lưu trữ, chia sẻ, cho xem, truyền nội dung, quản lý thư mục từ bạn bè, gửi email, lấy liên kết từ các tệp tin, dữ liệu có chứa nội dung vi phạm bản quyền hoặc tài sản độc quyền của cá nhân hay tập thể khác trong tài khoản của mình. + Tất cả các người sử dụng đều bị giám sát chặt chẽ khi dùng dịch vụ Ổ Mây MEGA nhằm để tránh việc lưu trữ và phân phát nội dung vi phạm bản quyền. Bạn (khách hàng hay người sử dụng) không được phép đăng tải, tải xuống, lưu trữ, chia sẻ, cho xem, truyền nội dung, quản lý thư mục từ bạn bè, gửi e-mail, lấy liên kết từ các tệp tin, dữ liệu có chứa nội dung vi phạm bản quyền hoặc tài sản độc quyền của cá nhân hay tập thể khác trong tài khoản của mình. Đồng ý @@ -1419,7 +1419,7 @@ Âm báo chát - Phần Thưởng Thành Tích + Thành Tích Mời bạn bè tham gia và nhận thưởng @@ -1439,7 +1439,7 @@ Thưởng tài khoản mới - Cài đặt App MEGA cho Máy Tính + Cài đặt App MEGA cho Máy Tính còn lại %1$d ngày @@ -1457,7 +1457,7 @@ Lời mời đã gửi đi - Địa chỉ email không đúng cú pháp + Địa chỉ e-mail không đúng cú pháp Sau khi cài đặt MEGA trên máy tính, bạn sẽ nhận được thêm %1$s vào không gian lưu trữ, có hạn sử dụng là 365 ngày. MEGA còn có apps dành cho Windows, macOS và đa số các máy Linux distros. @@ -1617,23 +1617,23 @@ Chia sẻ thông qua - [A]Thùng Rác[/A] [B]Trống[/B] + [A]Thùng Rác[/A] [B]trống[/B] [B]Không có tệp tin nào trong [/B][A]Ổ Mây[/A] [B]Không có tệp tin nào trong [/B][A]Lưu về Ngoại Tuyến[/A] - [B]Không có tên [/B][A]Liên Lạc[/A] + z[B]Không có tên [/B][A]liên lạc[/A] - [A]Không có[/A] [B]Cuộc Trò Chuyện[/B] nào + [A]Không có[/A] [B]cuộc trò chuyện[/B] nào - [A]Đang tải[/A] các [B]Cuộc Trò Chuyện…[/B] + [A]Đang tải[/A] các [B]cuộc trò chuyện…[/B] - [B]Không có [/B][A]Thư Mục Chia Sẻ Nhận Được[/A] + [B]Không có [/B][A]thư mục chia sẻ nhận được[/A] - [B]Không có [/B][A]Thư Mục Chia Sẻ Gửi Đi[/A] + [B]Không có [/B][A]thư mục chia sẻ gửI đI[/A] - [B]Không có [/B][A]Liên Kết Công Khai[/A] [B]nào[/B] + [B]Không có [/B][A]liên kết công khai[/A] [B]nào[/B] Yêu cầu gửi đi @@ -1758,7 +1758,7 @@ Lỗi. Nội dung không còn tồn tại nữa - Bật Thông Báo + Bật thông báo Bằng cách này, các tin nhắn mới sẽ hiển thị\ntrên điện thoại Android ngày tức thì. @@ -1782,7 +1782,7 @@ Bạn có còn nhớ mật khẩu của mình không? - Trước khi tiến hành việc đăng xuất, xin thử điền mật khẩu để kiểm tra rằng mình còn nhớ chính xác.\nQuên mất mật khẩu và không có mã phục hồi sẽ không lấy lại được tài khoản. + Trước khi tiến hành việc đăng xuất, xin thử điền mật khẩu để kiểm tra rằng mình còn nhớ chính xác.\nQuên mất mật khẩu bạn sẽ mất quyền truy cập vào dữ liệu MEGA của mình. Xin thử nhập mật khẩu để kiểm tra rằng mình còn nhớ mật khẩu. Quên mất mật khẩu đồng nghĩa với việc mất toàn quyền truy cập dữ liệu. @@ -1792,13 +1792,13 @@ Mật khẩu chính xác - Mật khẩu không đúng.\nLưu lại Mã Phục Hồi ngay lập tức! + Mật khẩu không đúng.\nHãy lưu lại Chìa Khóa Phục Hồi ngay lập tức! Ghi vào bảng nhớ tạm Tiến hành đăng xuất - Mã Phục Hồi + Chìa Khóa Phục Hồi Lưu vào File System @@ -1818,9 +1818,9 @@ Bảo Mật - Sao lưu Mã Phục Hồi + Sao lưu Chìa Khóa Phục Hồi - Xuất ra Mã Phục Hồi và giữ mã ở một nơi an toàn và dễ nhớ sẽ giúp bạn tránh việc mất dữ liệu khi quên mật khẩu. + Xuất ra Chìa Khóa Phục Hồi và giữ chìa khóa này ở một nơi an toàn và dễ nhớ sẽ giúp bạn tránh việc mất dữ liệu khi quên mật khẩu. Không liên lạc được tới MEGA. Xin kiểm tra kết nối mạng hoặc thử lại sau. @@ -1836,7 +1836,7 @@ In - Mã Phục Hồi đã được lưu vào máy + Chìa Khóa Phục Hồi đã được lưu lại thành công (Đang chờ) @@ -1880,7 +1880,7 @@ Xác thực 2 yếu tố là một lớp bảo mật thứ hai cho tài khoản của bạn. Nghĩa là trong trường hợp có người biết mật khẩu muốn lén vào tài khoản sẽ không thể vào được, và buộc phải nhập thêm đoạn mã 6 chữ số mà chỉ chủ sở hữu tài khoản mới có. - Bắt Đầu Thiết Lập + Bắt đầu thiết lập Quét hoặc sao chép đoạn chủng tử này vào app lập xác thực. @@ -1898,7 +1898,7 @@ Vào những lần đăng nhập tiếp theo, hệ thống sẽ yêu cầu nhập một đoạn mã dài 6 chữ số lấy từ app lập xác thực. - Xin lưu Mã Phục Hồi cho tài khoản của bạn ở nơi an toàn, để tránh trường hợp mất khả năng truy cập vào ứng dụng, hoặc khi bạn muốn tắt đi xác thực 2 yếu tố. + Xin lưu Chìa Khóa Phục Hồi của tài khoản của bạn ở nơi an toàn, để tránh trường hợp mất khả năng truy cập vào ứng dụng, hoặc khi bạn muốn tắt đi xác thực 2 yếu tố. Mã không phù hợp @@ -1908,7 +1908,7 @@ Đổi mật khẩu - Đổi email + Đổi e-mail Xoá tài khoản @@ -1928,7 +1928,7 @@ Đóng - Xuất ra Mã Phục Hồi để hoàn tất + Xuất ra Chìa Khóa Phục Hồi để hoàn tất App Xác thực 2 yếu tố @@ -1936,7 +1936,7 @@ CH Play - Cần phải có một app lập xác thực để bật tính năng XT2B với MEGA. Bạn có thể tải và cài đặt Google Authenticator, Duo Mobile, Authy hoặc Microsoft Authenticator app về thiết bị của mình. + Cần phải có một app lập xác thực để bật tính năng 2FA với MEGA. Bạn có thể tải và cài đặt Google Authenticator, Duo Mobile, Authy hoặc Microsoft Authenticator app về thiết bị của mình. Chia sẻ %d tệp tin chưa được hoàn tất @@ -1982,7 +1982,7 @@ Lỗi phát sinh. Mục không thể khôi phục. - Gửi Tin Nhắn + Gửi tin nhắn Hành động này không thể hoàn tất vì sẽ chiếm dụng toàn bộ không gian lưu trữ của tài khoản. Có muốn tiến hành nâng cấp hạng tài khoản để tiếp tục hay không? @@ -2032,7 +2032,7 @@ Có %1$d phiên bản lịch sử tệp tin, chiếm dụng %2$s - Quản Lý Tệp Tin + Quản lý tệp tin Xóa hết tất cả các phiên bản cũ đang lưu @@ -2048,7 +2048,7 @@ Kích hoạt hoặc tắt tính năng ghi nhớ lịch sử phên bản cho toàn bộ tài khoản.\nTắt đi tính năng ghi phiên bản sẽ không ngăn cản các tên liên lạc trong tài khoản của bạn tạo ra phiên bản mới của thư mục chia sẻ chung. - Chạm vào đây, nhập tên hay email + Chạm vào đây, nhập tên hay e-mail Thêm %s vào sổ liên lạc? @@ -2068,7 +2068,7 @@ Xin nhập mật khẩu để xác thực tài khoản - Không cần phải nhập chính email của mình vào sổ liên lạc + Không cần phải nhập chính e-mail của mình vào sổ liên lạc Mã không phù hợp @@ -2154,13 +2154,13 @@ Lấy liên kết vào cuộc chát - Bật Luân Phiên Mã Khóa + Bật Luân Phiên Chìa Khóa Mã - Luân phiên mã khóa được bật + Luân phiên chìa khóa mã được bật - Luân phiên mã khóa có an toàn hơn một chút, nhưng không cho phép bạn tạo liên kết phòng chát và những người mới tham gia sẽ không đọc được các tin nhắn cũ. + Luân phiên chìa khóa mã có an toàn hơn một chút, nhưng không cho phép bạn tạo liên kết phòng chát và những người mới tham gia sẽ không đọc được các tin nhắn cũ. - Luân phiên mã khóa bị tắt đối với các cuộc trò chuyện có hơn 100 thành viên. + Luân phiên chìa khóa mã bị tắt đối với các cuộc trò chuyện có hơn 100 thành viên. Không thể chuyển đổi cuộc chát này sang chế độ riêng tư được vì đã vượt quá số lượng thành viên. @@ -2188,15 +2188,15 @@ Phần xem tổng quan của phòng chát không còn tồn tại. Nếu bạn thoát ra, bạn sẽ không mở lại được nữa. - [A]%1$s[/A][B] đã bật luân phiên mã khóa.[/B] + [A]%1$s[/A][B] đã bật luân phiên chìa khóa mã.[/B] Cuộc trò chuyện này không còn tồn tại nữa - Luân Phiên Mã Khóa + Luân phiên chìa khóa mã - Luân phiên mã khóa có an toàn hơn một chút, nhưng không cho phép bạn tạo liên kết phòng chát và những người mới tham gia sẽ không đọc được các tin nhắn cũ. + Luân phiên chìa khóa mã có an toàn hơn một chút, nhưng không cho phép bạn tạo liên kết phòng chát và những người mới tham gia sẽ không đọc được các tin nhắn cũ. - Luân phiên mã khóa có an toàn hơn một chút, nhưng không cho phép bạn tạo liên kết phòng chát và những người mới tham gia sẽ không đọc được các tin nhắn cũ. + Luân phiên chìa khóa mã có an toàn hơn một chút, nhưng không cho phép bạn tạo liên kết phòng chát và những người mới tham gia sẽ không đọc được các tin nhắn cũ. Lời mời đã được gửi tới tên liên lạc %s trước đây rồi. Theo dõi tình trạng của lời mời trong tab Yêu Cầu Gửi Đi. @@ -2250,7 +2250,7 @@ Thư mục này đã bị yêu cầu cần phải gỡ bỏ. - Yêu Cầu Gỡ Bỏ + Yêu cầu gỡ bỏ Không truy cập được do vi phạm Điều Khoản Dịch Vụ @@ -2324,7 +2324,7 @@ %s đã được nén xong - Đang Nén Videos %1$d/%2$d + Đang nén videos %1$d/%2$d Thư mục vừa chọn không hợp lệ @@ -2370,11 +2370,11 @@ Cuộc Gọi Đã Bắt Đầu - SSL key bị lỗi + Chìa khóa SSL bị lỗi MEGA không khởi tạo được kết nối an ninh qua phương thức SSL. Thiết bị bạn đang sử dụng có thể đang dùng mạng Wi-Fi công cộng có yêu cầu một số điều kiện khi kết nối. - [B]Không có [/B][A]Thông Báo[/A] + [B]Không có [/B][A]thông báo[/A] Thiếp Lập MEGA @@ -2432,7 +2432,7 @@ Ngày mai - [B]Không có [/B][A]Tệp Tin Chia Sẻ[/A] + [B]Không có [/B][A]tệp tin chia sẻ[/A] Không thể tham gia vào cuộc gọi do vượt quá số lượng tối đa thành viên tham gia. @@ -2470,7 +2470,7 @@ Tin nhắn đã được chuyển đi - Vị Trí Được Đánh Dấu + Vị trí được đánh dấu %d tệp tin chưa được gửi tới %d cuộc chát @@ -2550,7 +2550,7 @@ Việc phát nội dung phương tiện số không thực hiện được khi có cuộc gọi đang diễn ra. - Cuộc Gọi Đang Diễn Ra + Cuộc gọI đang diễn ra Chạm vào để tham gia cuộc gọi nhóm. @@ -2560,11 +2560,11 @@ Cuộc trò truyện này sẽ không thể truy cập được nữa ngay sau khi xóa bỏ liên kết vào chat. - Luân Phiên Mã Khóa sẽ không cho phép bạn đi vào liên kết tới chát mà bỏ qua việc tạo cuộc chát mới. + Luân Phiên Chìa Khóa Mã sẽ không cho phép bạn đi vào liên kết tới cuộc chát mà bỏ qua việc tạo nhóm chát mới. Bạn có muốn tạo ra phòng chát mới và lấy đường liên kết vào phòng chát hay không? - Luân phiên mã khóa có an toàn hơn một chút, nhưng không cho phép bạn tạo liên kết phòng chát và những người mới tham gia sẽ không đọc được các tin nhắn cũ. + Luân phiên chìa khóa mã có an toàn hơn một chút, nhưng không cho phép bạn tạo liên kết phòng chát và những người mới tham gia sẽ không đọc được các tin nhắn cũ. Bước %1$d trong tổng %2$d bước @@ -2728,19 +2728,19 @@ Lỗi. Thư mục %1$s chưa được tạo - Xác thực địa chỉ email của bạn + Xác thực địa chỉ e-mail của bạn Tài khoản đã tạm thời bị khóa để đảm bảo an toàn. - Xin làm theo hướng dẫn có trong [A]thư email xác thực[/A] để mở khóa tài khoản. + Xin làm theo hướng dẫn có trong [A]thư e-mail xác thực[/A] để mở khóa tài khoản. Tại sao tôi đang thấy cái này? - Gửi lại email + Gửi lại e-mail - Email đã được gửi đi rồi. Xin chờ vài phút trước khi gửi lại cái mới. + E-mail đã được gửi đi rồi. Xin chờ vài phút trước khi gửi lại cái mới. - Tài Khoản Bị Khóa + TàI khoản bị khóa Quý khách hiện tại có thể đang dùng một mật khẩu duy nhất cho tất cả các dịch vụ khác bao gồm cả MEGA, một trong các dịch vụ đó đã bị tấn công và để lộ thông tin gần đây. @@ -2802,15 +2802,15 @@ Tệp tin không có tìm thấy trong Ổ Mây của bạn. - Hết Không Gian Lưu Trữ + Hết không gian lưu trữ Dữ liệu của bạn có nguy cơ bị xóa! - Chúng tôi đã gửi thông báo vào địa chỉ email %1$s của quý khách vào ngày %2$s và ngày %3$s, nhưng quý khách vẫn còn %4$s tệp tin chiếm tới %5$s trong tài khoản MEGA, quý khách cần phải nâng câp lên %6$s để đáp ứng dung lượng. + Chúng tôi đã gửi thông báo vào địa chỉ e-mail %1$s của quý khách vào ngày %2$s và ngày %3$s, nhưng quý khách vẫn còn %4$s tệp tin chiếm tới %5$s trong tài khoản MEGA, quý khách cần phải nâng câp lên %6$s để đáp ứng dung lượng. - Chúng tôi đã gửi thông báo vào địa chỉ email %1$s của quý khách, nhưng quý khách vẫn còn %2$s tệp tin chiếm tới %3$s trong tài khoản MEGA, quý khách cần phải nâng câp lên %4$s để đáp ứng dung lượng. + Chúng tôi đã gửi thông báo vào địa chỉ e-mail %1$s của quý khách, nhưng quý khách vẫn còn %2$s tệp tin chiếm tới %3$s trong tài khoản MEGA, quý khách cần phải nâng câp lên %4$s để đáp ứng dung lượng. [B]Quý khách còn lại [M]%s[/M] để thực hiện nâng cấp[/B]. Nếu không sau đó dữ liệu sẽ bị xóa. @@ -2828,7 +2828,7 @@ %d giây - Kiểm tra chứng thực + Xác thực chứng thực Chưa có xác thực @@ -2978,15 +2978,15 @@ Không thay đổi được mức độ ưu tiên cho truyền tải “%1$s” - Gửi chìa khóa giải mã ra riêng + Gửi chìa khóa giải mã ra riêng - Xuất ra đường liên kết và mã giải khóa ở hai mục khác nhau. + Xuất đường liên kết và chìa khóa giải mã ra riêng rẽ. Tìm hiểu thêm - Chìa Khóa + Chìa khóa - Chia sẻ chìa khóa + Chia sẻ chìa khóa Sao chép chìa khóa @@ -3008,7 +3008,7 @@ Đặt lại mật khẩu - Chia sẻ chìa khóa cho đường liên kết này? + Chia sẻ chìa khóa cho đường liên kết này? Chia sẻ mật khẩu cho đường liên kết này? @@ -3226,7 +3226,7 @@ Mới Thêm Gần Đây - [B]Không có [/B][A]Nhóm[/A] + [B]Không có [/B][A]nhóm[/A] Yêu cầu @@ -3398,7 +3398,7 @@ Mở khóa với dấu vân tay cua bạn - Dùng mã số + Dùng mật mã Dấu vân tay của bạn đã được xác nhận @@ -3536,7 +3536,7 @@ Ưa Thích - [B]Không có[/B] [A]Mục Ưa Thích[/A] + [B]Không có[/B] [A]mục ưa thích[/A] Loại bỏ ưa thích @@ -3608,7 +3608,7 @@ Ưa Thích - Quên mất mã số? + Quên mất mật mã? Nhập mật khẩu @@ -3876,7 +3876,7 @@ Đây là thư mục chứa các tệp tin và thư mục đã được sao lưu dự phòng. Các mục lưu dự phòng là dạng “chỉ-được-xem” để phòng tránh bị chỉnh sửa trong Ổ Mây của bạn.Bạn có thể lưu dự phòng dữ liệu của mình từ máy tính lên MEGA bằng cách sử dụng App MEGA cho Máy Tính. - Tài khoản bị đình chỉ do vi phạm bản quyền. Chúng tôi đã gửi cho bạn một email với nhiều thông tin hơn về việc này. + Tài khoản bị đình chỉ do vi phạm bản quyền. Chúng tôi đã gửi cho bạn một e-mail với nhiều thông tin hơn về việc này. Tài khoản đã bị chấm dứt hoạt động do vi phạm lỗi có nêu trong Điều Khoản Dịch Vụ của MEGA; chẳng hạn như lạm dụng hệ thống, quyền hạn sử dụng của người khác hoặc chia sẻ nội dung cấm. @@ -4008,9 +4008,9 @@ Xác thực liên lạc - We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to verify the contacts you share information with before they can access the shared folders. + Chúng tôi bảo vệ dữ liệu của bạn bằng thuật toán mã hóa vô nhận thức. Để đảm bảo an toàn hơn, chúng tôi yêu cầu bạn xác thực các tên liên lạc mà bạn chia sẻ thông tin trước khi họ được cấp quyền truy cập vào các thư mục chia sẻ. - We protect your data with zero-knowledge encryption. To ensure extra security, we ask you to verify the contacts you receive information from before you can access the shared folders. + Chúng tôi bảo vệ dữ liệu của bạn bằng thuật toán mã hóa vô nhận thức. Để đảm bảo an toàn hơn, chúng tôi yêu cầu bạn xác thực các tên liên lạc mà bạn có nhận thông tin trước khi bạn truy cập vào các thư mục chia sẻ. Để xác thực tên liên lạc, đảm bảo rằng chứng thực bạn thấy dưới đây trùng khớp với chứng thực tài khoản của họ. Bạn có thể hỏi người đó chia sẻ chứng thực cho bạn. @@ -4022,5 +4022,12 @@ Chứng thực của bạn - Preparing file for preview + Đang chuẩn bị tệp để xem trước + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + Chi tiết \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings_sdk_errors.xml b/app/src/main/res/values-vi/strings_sdk_errors.xml index 4b24446920d..fb7a05e8b82 100644 --- a/app/src/main/res/values-vi/strings_sdk_errors.xml +++ b/app/src/main/res/values-vi/strings_sdk_errors.xml @@ -31,7 +31,7 @@ Chưa hoàn thoành - Chìa khóa không hợp lệ / Lỗi giải mã + Chìa khóa không hợp lệ hoặc lỗi giải mã Bad session ID diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index f5b5d3366fb..d30bb12b7ea 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -357,7 +357,7 @@ 正在创建文件夹 - 回收站为空 + 清空回收站 您即将永久移除回收站中的所有项目。 @@ -4004,7 +4004,7 @@ 相册将会被删除但是它里面的内容将保留在您的时间线中。 - See less + 查看更少 联系人验证 @@ -4014,13 +4014,20 @@ 为了验证您的联系人,请确保您在上面看到的凭证与他们的账户凭证相符。您可以要求他们与您分享他们的凭证。 - [Undecrypted folder] + [未解密的文件夹] - To access the shared folder, the person who shared it with you should verify you, too. + 为了访问此共享文件夹,与您分享它的人应该也对您进行验证。 - Verify %s + 验证%s 您的凭证 - Preparing file for preview + 正在为预览准备文件 + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + 描述 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b210889fbda..75cfedefdf9 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -714,7 +714,7 @@ 檢視來源碼 - ZIP Browser + ZIP瀏覽器 免費 @@ -2807,10 +2807,10 @@ 您的資料正面臨風險 - 我們已於%2$s與%3$s透過email至%1$s聯繫您,但您的MEGA帳戶仍然有%4$s個檔案共佔用%5$s,您需要升級至%6$s。 + 我們已在%2$s及%3$s透過電子郵件寄信至%1$s聯繫您,但您的MEGA帳戶仍然有%4$s個檔案共佔用%5$s,您需要升級至%6$s。 - 我們已透過email至%1$s聯繫您,但您的MEGA帳戶仍然有%2$s個檔案共佔用%3$s,您需要升級至%4$s。 + 我們已透過電子郵件寄信至%1$s聯繫您,但您的MEGA帳戶仍然有%2$s個檔案共佔用%3$s,您需要升級至%4$s。 [B]您剩下[M]%s[/M]可以升級[/B]。在這之後您的資料將被刪除。 @@ -4022,5 +4022,12 @@ 您的憑證 - Preparing file for preview + 正在準備檔案預覽 + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + 說明 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3aac414e972..44c665b46de 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -258,7 +258,7 @@ Permissions - Share Permissions + Share permissions Change permissions @@ -675,7 +675,7 @@ Local Secondary folder - MEGA Secondary folder + MEGA secondary folder Photos only @@ -726,7 +726,7 @@ View source code - ZIP Browser + ZIP browser Free @@ -794,9 +794,9 @@ Change - YES + Yes - NO + No Forgot your password? @@ -882,7 +882,7 @@ Set password protection - (PRO ONLY) + (Pro only) Set password @@ -1067,7 +1067,7 @@ Edit profile - Leave Group + Leave group Participants @@ -1660,17 +1660,17 @@ [B]No files [/B][A]Saved for Offline[/A] - [B]No [/B][A]Contacts[/A] + [B]No [/B][A]contacts[/A] - [A]No[/A] [B]Conversations[/B] + [A]No[/A] [B]conversations[/B] - [A]Loading[/A] [B]Conversations…[/B] + [A]Loading[/A] [B]conversations…[/B] - [B]No [/B][A]Incoming Shared folders[/A] + [B]No [/B][A]incoming shared folders[/A] - [B]No [/B][A]Outgoing Shared folders[/A] + [B]No [/B][A]outgoing shared folders[/A] - [B]No [/B][A]Public Links[/A][B][/B] + [B]No [/B][A]public links[/A][B][/B] Sent requests @@ -1801,7 +1801,7 @@ Error. The resources are no longer available - Turn on Notifications + Turn on notifications This way, you will see new messages\non your Android phone instantly. @@ -1884,7 +1884,7 @@ The Recovery key has been successfully saved - (Pending) + (pending) Rich URL Previews @@ -1927,7 +1927,7 @@ Two-factor authentication is a second layer of security for your account. Which means that even if someone knows your password they cannot access it, without also having access to the 6-digit code only you have access to. - Begin Setup + Begin setup Scan or copy the seed to your authenticator app. @@ -2030,7 +2030,7 @@ An error occurred. Item not restored. - Send Message + Send message This action cannot be completed as it would take you over your current storage limit. Would you like to upgrade your account? @@ -2081,7 +2081,7 @@ %1$d file versions, taking a total of %2$s - File Management + File management Delete all older versions of my files @@ -2300,7 +2300,7 @@ This folder has been the subject of a takedown notice. - Dispute Takedown + Dispute takedown Not accessible as it violated our Terms of Service @@ -2375,7 +2375,7 @@ %s has been compressed - Compressing Videos %1$d/%2$d + Compressing videos %1$d/%2$d Invalid folder selected @@ -2432,7 +2432,7 @@ MEGA is unable to establish a secure connection using SSL. You may be on a public Wi-Fi network with additional requirements. - [B]No [/B][A]Notifications[/A] + [B]No [/B][A]notifications[/A] Set up MEGA @@ -2493,7 +2493,7 @@ Tomorrow - [B]No [/B][A]Shared Files[/A] + [B]No [/B][A]shared files[/A] Call cannot be joined as the maximum number of participants has been exceeded. @@ -2532,7 +2532,7 @@ Messages forwarded - Pinned Location + Pinned location %d file was not sent to %d chats @@ -2618,7 +2618,7 @@ It is not possible to play media files while there is a call in progress. - Ongoing Call + Ongoing call Tap to join current group call. @@ -2795,7 +2795,7 @@ Please select one or more contacts. - Sent %s Contacts. + Sent %s contacts. My chat files @@ -2813,7 +2813,7 @@ Email already sent. Please wait a few minutes before trying again. - Locked Accounts + Locked accounts It is possible that you are using the same password for your MEGA account as for other services, and that at least one of these other services has suffered a data breach. @@ -2876,7 +2876,7 @@ The file cannot be found in your Cloud drive. - Storage Full + Storage full Your data is at risk! @@ -3321,7 +3321,7 @@ Recently Added - [B]No [/B][A]Groups[/A] + [B]No [/B][A]groups[/A] Requests @@ -3634,7 +3634,7 @@ Favourites - [B]No[/B] [A]Favourites[/A] + [B]No[/B] [A]favourites[/A] Remove favourites @@ -4133,4 +4133,11 @@ Your credentials Preparing file for preview + + + Bonus expires in %1$d day + Bonus expires in %1$d days + + + Description \ No newline at end of file From c9c3fdb934ddc086eba74584b62c07ef383cd07e Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Wed, 11 Jan 2023 19:29:56 +1300 Subject: [PATCH 021/334] AND-14572: Create a new pipeline that can be triggered after each merge to dev --- app/build.gradle | 2 ++ jenkinsfile/android_release.groovy | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index a87c78284b9..bfc62711787 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -18,8 +18,10 @@ apply from: "$project.rootDir/tools/util.gradle" def readVersionCode() { def versionCode = System.getenv('APK_VERSION_CODE_FOR_CD') if (versionCode != null && !versionCode.isEmpty()) { + // Jenkins build will compute version code using this variable return versionCode.toInteger() } else { + // Local build will compute version code using this function return new Date().format("yyDDDHHmm", TimeZone.getTimeZone("GMT")).toInteger() } } diff --git a/jenkinsfile/android_release.groovy b/jenkinsfile/android_release.groovy index 209fd10d4c2..f6d5dc97734 100644 --- a/jenkinsfile/android_release.groovy +++ b/jenkinsfile/android_release.groovy @@ -59,6 +59,9 @@ pipeline { CONSOLE_LOG_FILE = 'console.txt' + // CD pipeline uses this environment variable to assign version code + APK_VERSION_CODE_FOR_CD = "${new Date().format('yyDDDHHmm', TimeZone.getTimeZone("GMT"))}" + BUILD_LIB_DOWNLOAD_FOLDER = '${WORKSPACE}/mega_build_download' } post { @@ -701,12 +704,13 @@ private void checkSDKVersion() { } } -/** - * read the version name and version code from source code(build.gradle) + * read the version name from source code(build.gradle) + * read the version code from environment variable + * * @return a tuple of version code and version name */ def readAppVersion() { - String versionCode = sh(script: "grep versionCode build.gradle | awk -F= '{print \$2}'", returnStdout: true).trim() + String versionCode = APK_VERSION_CODE_FOR_CD String versionName = sh(script: "grep appVersion build.gradle | awk -F= '{print \$2}'", returnStdout: true).trim().replaceAll("\"", "") return [versionName, versionCode] } From 162cbed5daf3dddb48e47d3c8da48531af4b2a55 Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Wed, 11 Jan 2023 20:50:23 +1300 Subject: [PATCH 022/334] AND-14572: Create a new pipeline that can be triggered after each merge to dev --- jenkinsfile/android_release.groovy | 1 + 1 file changed, 1 insertion(+) diff --git a/jenkinsfile/android_release.groovy b/jenkinsfile/android_release.groovy index f6d5dc97734..421fc4e47bf 100644 --- a/jenkinsfile/android_release.groovy +++ b/jenkinsfile/android_release.groovy @@ -704,6 +704,7 @@ private void checkSDKVersion() { } } +/** * read the version name from source code(build.gradle) * read the version code from environment variable * From 0f4cbb4b3152f7fd62bda8dfb2e13cec038933d5 Mon Sep 17 00:00:00 2001 From: Gregg Meyrick Jover Date: Fri, 13 Jan 2023 19:20:15 +1300 Subject: [PATCH 023/334] CU-161: Fix Successful Uploads Not Being Shown in Completed Transfers Page --- .../app/jobservices/CameraUploadsService.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt b/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt index 68c028e4908..e6aac6d015c 100644 --- a/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt +++ b/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt @@ -22,6 +22,8 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import mega.privacy.android.app.AndroidCompletedTransfer +import mega.privacy.android.app.LegacyDatabaseHandler import mega.privacy.android.app.MegaApplication import mega.privacy.android.app.MimeTypeList import mega.privacy.android.app.R @@ -46,6 +48,7 @@ import mega.privacy.android.app.domain.usecase.SetOriginalFingerprint import mega.privacy.android.app.domain.usecase.SetPrimarySyncHandle import mega.privacy.android.app.domain.usecase.SetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.StartUpload +import mega.privacy.android.app.globalmanagement.TransfersManagement.Companion.addCompletedTransfer import mega.privacy.android.app.listeners.GetCameraUploadAttributeListener import mega.privacy.android.app.listeners.SetAttrUserListener import mega.privacy.android.app.main.ManagerActivity @@ -381,6 +384,12 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, @Inject lateinit var getDefaultNodeHandle: GetDefaultNodeHandle + /** + * LegacyDatabaseHandler + */ + @Inject + lateinit var tempDbHandler: LegacyDatabaseHandler + /** * AreAllUploadTransfersPaused */ @@ -1681,6 +1690,13 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, return } + if (transfer.state == MegaTransfer.STATE_COMPLETED) { + addCompletedTransfer( + AndroidCompletedTransfer(transfer, e), + tempDbHandler + ) + } + if (e.errorCode == MegaError.API_OK) { Timber.d("Image Sync API_OK") val node = getNodeByHandle(transfer.nodeHandle) From b6b07b888d69380a4e961bb2d5708aedfcd4c34f Mon Sep 17 00:00:00 2001 From: Yenel Date: Fri, 13 Jan 2023 13:26:58 +0100 Subject: [PATCH 024/334] AP-71 Stuck on "Connecting to Server" --- .../java/mega/privacy/android/app/main/LoginActivity.kt | 7 +++++++ .../java/mega/privacy/android/app/main/LoginFragment.kt | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/LoginActivity.kt b/app/src/main/java/mega/privacy/android/app/main/LoginActivity.kt index 164bff5a9e7..3edf0fbbdea 100644 --- a/app/src/main/java/mega/privacy/android/app/main/LoginActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/LoginActivity.kt @@ -164,6 +164,8 @@ class LoginActivity : BaseActivity(), MegaRequestListenerInterface { if (viewModel.intentAction == ACTION_FORCE_RELOAD_ACCOUNT) { MegaApplication.isLoggingIn = false finish() + } else if (viewModel.intentAction == ACTION_OPEN_APP) { + loginFragment?.readyToManager() } } }, IntentFilter(ACTION_FETCH_NODES_FINISHED)) @@ -548,6 +550,11 @@ class LoginActivity : BaseActivity(), MegaRequestListenerInterface { */ const val ACTION_FORCE_RELOAD_ACCOUNT = "FORCE_RELOAD" + /** + * Intent action for opening app. + */ + const val ACTION_OPEN_APP = "OPEN_APP" + /** * Intent action for notifying fetchNodes finished. */ diff --git a/app/src/main/java/mega/privacy/android/app/main/LoginFragment.kt b/app/src/main/java/mega/privacy/android/app/main/LoginFragment.kt index 81b06d5d986..42039ac49ab 100644 --- a/app/src/main/java/mega/privacy/android/app/main/LoginFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/main/LoginFragment.kt @@ -55,6 +55,7 @@ import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.listeners.ChatLogoutListener import mega.privacy.android.app.logging.LegacyLoggingSettings import mega.privacy.android.app.main.LoginActivity.Companion.ACTION_FORCE_RELOAD_ACCOUNT +import mega.privacy.android.app.main.LoginActivity.Companion.ACTION_OPEN_APP import mega.privacy.android.app.main.controllers.AccountController.Companion.localLogoutApp import mega.privacy.android.app.presentation.extensions.getFormattedStringOrDefault import mega.privacy.android.app.presentation.login.LoginViewModel @@ -956,6 +957,7 @@ class LoginFragment : Fragment(), MegaRequestListenerInterface { megaChatApi.refreshUrl() } } else { + viewModel.intentAction = ACTION_OPEN_APP Timber.w("Another login is processing") } } @@ -1328,7 +1330,7 @@ class LoginFragment : Fragment(), MegaRequestListenerInterface { /** * Launches Manager activity. */ - private fun readyToManager() { + fun readyToManager() { confirmLogoutDialog?.dismiss() val loginActivity = requireActivity() as LoginActivity From 2add54518783809e5097d1965a966bd12fb9e73e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yenel=20Rodr=C3=ADguez=20Hern=C3=A1ndez?= Date: Thu, 19 Jan 2023 04:25:44 +1300 Subject: [PATCH 025/334] AP-71 Fix crash calling LoginFragment.readyToManager() --- .../privacy/android/app/main/LoginActivity.kt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/LoginActivity.kt b/app/src/main/java/mega/privacy/android/app/main/LoginActivity.kt index 3edf0fbbdea..928ac37b845 100644 --- a/app/src/main/java/mega/privacy/android/app/main/LoginActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/LoginActivity.kt @@ -161,11 +161,18 @@ class LoginActivity : BaseActivity(), MegaRequestListenerInterface { registerReceiver(object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { - if (viewModel.intentAction == ACTION_FORCE_RELOAD_ACCOUNT) { - MegaApplication.isLoggingIn = false - finish() - } else if (viewModel.intentAction == ACTION_OPEN_APP) { - loginFragment?.readyToManager() + when (viewModel.intentAction) { + ACTION_FORCE_RELOAD_ACCOUNT -> { + MegaApplication.isLoggingIn = false + finish() + } + ACTION_OPEN_APP -> { + if (loginFragment?.isAdded == true) { + loginFragment?.readyToManager() + } else { + finish() + } + } } } }, IntentFilter(ACTION_FETCH_NODES_FINISHED)) From 87b8c897b76191ada769e1d5969cc1fec96d019f Mon Sep 17 00:00:00 2001 From: rohitsoni Date: Wed, 25 Jan 2023 10:57:02 +0530 Subject: [PATCH 026/334] AND-15494 Navigating to other tabs and selecting cloud drive hiding bottom navigation --- .../app/presentation/clouddrive/FileBrowserFragment.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt index 8b38f426a8e..3910e7fa9fb 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt @@ -1418,9 +1418,9 @@ class FileBrowserFragment : RotatableFragment() { private fun selectNewlyAddedNodes() { val positions = (requireActivity() as ManagerActivity).getPositionsList(fileBrowserViewModel.state.value.nodes) - .takeUnless { it.isEmpty() } + .takeUnless { it.isEmpty() } ?: return activateActionMode() - positions?.forEach { + positions.forEach { if (isMultipleselect) { adapter?.toggleSelection(it) } @@ -1429,7 +1429,7 @@ class FileBrowserFragment : RotatableFragment() { if (selectedNodes?.isNotEmpty() == true) { updateActionModeTitle() } - recyclerView?.scrollToPosition(positions?.minOrNull() ?: 0) + recyclerView?.scrollToPosition(positions.minOrNull() ?: 0) } private var nodePosition = 0 From f32fb8321c1aba03475152c6ff493b6ba832ccd7 Mon Sep 17 00:00:00 2001 From: Luong Hai Date: Thu, 26 Jan 2023 20:56:24 +1300 Subject: [PATCH 027/334] AND-15390: Fix reset version info --- .../SettingsFileManagementFragment.kt | 8 +------- .../filesettings/FilePreferencesViewModel.kt | 13 +++++++++++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/SettingsFileManagementFragment.kt b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/SettingsFileManagementFragment.kt index 873db90d196..fd81d344d07 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/SettingsFileManagementFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/SettingsFileManagementFragment.kt @@ -225,13 +225,7 @@ class SettingsFileManagementFragment : SettingsBaseFragment() { * Method for reset the version information. */ fun resetVersionsInfo() { - val text = StringResourcesUtils.getQuantityString( - R.plurals.settings_file_management_file_versions_subtitle, - 0, - 0, - "0 B" - ) - fileVersionsFileManagement?.summary = text + viewModel.resetVersionsInfo() clearVersionsFileManagement?.let { preferenceScreen.removePreference(it) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/settings/filesettings/FilePreferencesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/settings/filesettings/FilePreferencesViewModel.kt index e98ad7cd9b2..250932f726a 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/settings/filesettings/FilePreferencesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/settings/filesettings/FilePreferencesViewModel.kt @@ -73,6 +73,19 @@ class FilePreferencesViewModel @Inject constructor( } } + /** + * Reset versions info + * + */ + fun resetVersionsInfo() { + _state.update { + it.copy( + numberOfPreviousVersions = 0, + sizeOfPreviousVersionsInBytes = 0 + ) + } + } + /** * Enable file version option * From 7ca01486f07f7c16c85b030955f08a07e1ae3fe8 Mon Sep 17 00:00:00 2001 From: Gregg Meyrick Jover Date: Thu, 26 Jan 2023 20:23:40 +0800 Subject: [PATCH 028/334] CU-160: Fix Camera Uploads Not Working for Newly Created Accounts When Enabled in Timeline The issue stems from DefaultSetupPrimaryFolder, where the Use Case attempted to restart Camera Upload by calling Use Cases StopCameraUpload and StartCameraUpload, consecutively. This caused a race condition; while StopCameraUpload was still running its underlying Worker, StartCameraUpload also ran its Worker, and was unable to start Camera Uploads because the feature was still enabled. In order to resolve this race condition, the underlying Workers StopCameraUploadWorker and StartCameraUploadWorker must be executed sequentially through Worker chaining. Changelog: - Create fireRestartCameraUploadJob in JobUtil. The function chains two Workers StopCameraUploadWorker and StartCameraUploadWorker to restart Camera Uploads. - Create Use Case RestartCameraUpload that implements JobUtil.fireRestartCameraUploadJob. - In DefaultSetupPrimaryFolder, replace the independent Use Case calls StopCameraUpload and StartCameraUpload with RestartCameraUpload instead. - Update DefaultSetupPrimaryFolderTest. - Add documentation in several classes. - Add additional Timber logs in several classes improve debugging. - Remove warnings in affected classes. --- .../di/cameraupload/CameraUploadUseCases.kt | 20 +++++++++ .../app/jobservices/CameraUploadsService.kt | 44 ++++++++++++++----- .../jobservices/StartCameraUploadWorker.kt | 28 ++++++++---- .../GetCameraUploadAttributeListener.kt | 12 +++-- .../android/app/utils/CameraUploadUtil.java | 3 -- .../privacy/android/app/utils/JobUtil.java | 28 +++++++++++- .../app/utils/wrapper/JobUtilWrapper.kt | 27 ++++++++++++ .../app/di/TestCameraUploadUseCases.kt | 4 ++ .../usecase/DefaultSetupPrimaryFolder.kt | 5 +-- .../domain/usecase/RestartCameraUpload.kt | 14 ++++++ .../usecase/DefaultSetupPrimaryFolderTest.kt | 9 ++-- 11 files changed, 159 insertions(+), 35 deletions(-) create mode 100644 domain/src/main/kotlin/mega/privacy/android/domain/usecase/RestartCameraUpload.kt diff --git a/app/src/main/java/mega/privacy/android/app/di/cameraupload/CameraUploadUseCases.kt b/app/src/main/java/mega/privacy/android/app/di/cameraupload/CameraUploadUseCases.kt index b6eaf9403bf..22db197f3e8 100644 --- a/app/src/main/java/mega/privacy/android/app/di/cameraupload/CameraUploadUseCases.kt +++ b/app/src/main/java/mega/privacy/android/app/di/cameraupload/CameraUploadUseCases.kt @@ -125,6 +125,7 @@ import mega.privacy.android.domain.usecase.ResetCameraUploadTimelines import mega.privacy.android.domain.usecase.ResetMediaUploadTimeStamps import mega.privacy.android.domain.usecase.ResetPrimaryTimeline import mega.privacy.android.domain.usecase.ResetSecondaryTimeline +import mega.privacy.android.domain.usecase.RestartCameraUpload import mega.privacy.android.domain.usecase.RestorePrimaryTimestamps import mega.privacy.android.domain.usecase.RestoreSecondaryTimestamps import mega.privacy.android.domain.usecase.SaveSyncRecord @@ -231,6 +232,25 @@ abstract class CameraUploadUseCases { ) } + /** + * Provides the [RestartCameraUpload] implementation + * + * @param jobUtilWrapper [JobUtilWrapper] + * @param context [Context] + * + * @return [RestartCameraUpload] + */ + @Provides + fun provideRestartCameraUpload( + jobUtilWrapper: JobUtilWrapper, + @ApplicationContext context: Context, + ): RestartCameraUpload = RestartCameraUpload { shouldIgnoreAttributes -> + jobUtilWrapper.fireRestartCameraUploadJob( + context = context, + shouldIgnoreAttributes = shouldIgnoreAttributes, + ) + } + /** * Provide the [MediaLocalPathExists] implementation */ diff --git a/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt b/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt index 36552b08e5c..cfff344a831 100644 --- a/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt +++ b/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt @@ -774,9 +774,11 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, runCatching { val state = canRunCameraUploads() if (state == StartCameraUploadsState.CAN_RUN_CAMERA_UPLOADS) { + Timber.d("Calling startWorker() successful. Starting Camera Uploads") hideLocalFolderPathNotifications() startCameraUploads() } else { + Timber.w("Calling startWorker() failed. Proceed to handle error") handleFailedStartCameraUploadsState(state) } }.onFailure { exception -> @@ -838,6 +840,7 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, * @param state The failing [StartCameraUploadsState] */ private suspend fun handleFailedStartCameraUploadsState(state: StartCameraUploadsState) { + Timber.w("Start Camera Uploads Error state: $state") when (state) { StartCameraUploadsState.MISSING_PREFERENCES, StartCameraUploadsState.DISABLED_SYNC, @@ -846,23 +849,29 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, StartCameraUploadsState.MISSING_LOCAL_PATH, StartCameraUploadsState.UNSATISFIED_WIFI_CONSTRAINT, -> { + Timber.e("Stop Camera Uploads due to $state") endService() } StartCameraUploadsState.MISSING_LOCAL_PRIMARY_FOLDER -> { + Timber.e("Local Primary Folder is disabled. Stop Camera Uploads") handleLocalPrimaryFolderDisabled() endService() } StartCameraUploadsState.MISSING_LOCAL_SECONDARY_FOLDER -> { + Timber.e("Local Secondary Folder is disabled. Stop Camera Uploads") handleLocalSecondaryFolderDisabled() endService() } StartCameraUploadsState.LOGGED_OUT_USER -> { + Timber.w("User is logged out. Perform a Complete Fast Login") performCompleteFastLogin() } StartCameraUploadsState.MISSING_USER_ATTRIBUTE -> { + Timber.w("Handle the missing Camera Uploads user attribute") handleMissingCameraUploadsUserAttribute() } StartCameraUploadsState.UNESTABLISHED_FOLDERS -> { + Timber.w("Primary and/or Secondary Folders do not exist. Establish the folders") establishFolders() } else -> Unit @@ -905,9 +914,10 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, * * @return true if the User is online, and false if otherwise */ + @Suppress("DEPRECATION") private fun isUserOnline(): Boolean = Util.isOnline(applicationContext).also { - if (!it) Timber.w("User is not online") + if (!it) Timber.w("User is offline") } /** @@ -917,7 +927,7 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, */ private suspend fun hasCameraUploadsLocalPath(): Boolean = !localPath().isNullOrBlank().also { - if (!it) Timber.w("Camera Uploads local path is empty") + if (it) Timber.w("Camera Uploads local path is empty") } /** @@ -1286,9 +1296,10 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, val transfer = globalTransfer.transfer val error = globalTransfer.error - Timber.d("Image Sync Finished, Error Code: ${error.errorCode}, " + - "Image Handle: ${transfer.nodeHandle}, " + - "Image Size: ${transfer.transferredBytes}" + Timber.d( + "Image Sync Finished, Error Code: ${error.errorCode}, " + + "Image Handle: ${transfer.nodeHandle}, " + + "Image Size: ${transfer.transferredBytes}" ) try { coroutineScope?.launch { @@ -1454,7 +1465,7 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, Timber.d("Start CameraUploadsService") startWorker() } else { - Timber.e("Complete Fast Login procedure unsuccessful with error ${result.exceptionOrNull()}") + Timber.e("Complete Fast Login procedure unsuccessful with error ${result.exceptionOrNull()}. Stop CameraUploadsService") endService() } } @@ -1525,6 +1536,7 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, */ private suspend fun setupPrimaryFolder() { val primaryHandle = getPrimaryFolderHandle().also { + Timber.d("Primary Handle retrieved from getPrimaryFolderHandle(): $it ") setPrimarySyncHandle(it) } // isCreatingPrimary is a flag that prevents creating a duplicate Primary Folder @@ -1551,6 +1563,7 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, private fun createAndSetupPrimaryUploadFolder() { primaryFolderCreationJob = coroutineScope?.launch { createCameraUploadFolder(getString(R.string.section_photo_sync))?.let { + Timber.d("Primary Folder successfully created with handle $it. Setting up Primary Folder") setupPrimaryFolder(it) } ?: endService() } @@ -1641,7 +1654,11 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, fun onGetPrimaryFolderAttribute(handle: Long, shouldStart: Boolean) { isPrimaryHandleSynced = true coroutineScope?.launch { - if (getPrimarySyncHandle() != handle) { + Timber.d("Current Handle: $handle, Should Start Camera Uploads Worker: $shouldStart") + val primarySyncHandle = getPrimarySyncHandle() + Timber.d("Primary Sync Handle: $primarySyncHandle") + if (primarySyncHandle != handle) { + Timber.w("Current and Primary Sync Handles are not equal. Set Primary Sync Handle to Current Handle $handle") setPrimarySyncHandle(handle) } if (shouldStart) { @@ -1704,9 +1721,14 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, val record = getSyncRecordByPath(path, isSecondary) if (record != null) { node?.let { nonNullNode -> - onUploadSuccess(node = nonNullNode, isSecondary = record.isSecondary) - handleSetOriginalFingerprint(node = nonNullNode, - originalFingerprint = record.originFingerprint.orEmpty()) + onUploadSuccess( + node = nonNullNode, + isSecondary = record.isSecondary, + ) + handleSetOriginalFingerprint( + node = nonNullNode, + originalFingerprint = record.originFingerprint.orEmpty(), + ) record.latitude?.let { latitude -> record.longitude?.let { longitude -> megaApi.setNodeCoordinates( @@ -2007,7 +2029,7 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, message = getString(R.string.download_preparing_files) } else { val inProgress = if (pendingTransfers == 0) { - totalTransfers - pendingTransfers + totalTransfers } else { totalTransfers - pendingTransfers + 1 } diff --git a/app/src/main/java/mega/privacy/android/app/jobservices/StartCameraUploadWorker.kt b/app/src/main/java/mega/privacy/android/app/jobservices/StartCameraUploadWorker.kt index a36e5b55bf6..695d84e8bc3 100644 --- a/app/src/main/java/mega/privacy/android/app/jobservices/StartCameraUploadWorker.kt +++ b/app/src/main/java/mega/privacy/android/app/jobservices/StartCameraUploadWorker.kt @@ -2,6 +2,7 @@ package mega.privacy.android.app.jobservices import android.content.Context import android.content.Intent +import android.os.Build import androidx.core.content.ContextCompat import androidx.hilt.work.HiltWorker import androidx.work.Worker @@ -13,6 +14,9 @@ import mega.privacy.android.app.jobservices.CameraUploadsService.Companion.EXTRA import mega.privacy.android.app.utils.JobUtil.IS_PRIMARY_HANDLE_SYNC_DONE import mega.privacy.android.app.utils.JobUtil.SHOULD_IGNORE_ATTRIBUTES import mega.privacy.android.app.utils.permission.PermissionUtilWrapper +import mega.privacy.android.app.utils.permission.PermissionUtils.getImagePermissionByVersion +import mega.privacy.android.app.utils.permission.PermissionUtils.getNotificationsPermission +import mega.privacy.android.app.utils.permission.PermissionUtils.getVideoPermissionByVersion import mega.privacy.android.app.utils.wrapper.JobUtilWrapper import timber.log.Timber @@ -21,7 +25,7 @@ import timber.log.Timber * Starts if: * 1. The service is not already running * 2. The storage is not over quota - * 3. The app has READ_EXTERNAL_STORAGE permission + * 3. The app has denied the Media Permissions */ @HiltWorker class StartCameraUploadWorker @AssistedInject constructor( @@ -43,13 +47,21 @@ class StartCameraUploadWorker @AssistedInject constructor( val isPrimaryHandleSynced = inputData.getBoolean(IS_PRIMARY_HANDLE_SYNC_DONE, false) return try { val isOverQuota = jobUtilWrapper.isOverQuota() - val permissions = arrayOf( - permissionUtilWrapper.getImagePermissionByVersion(), - permissionUtilWrapper.getVideoPermissionByVersion() - ) - val hasReadPermission = permissionUtilWrapper.hasPermissions(appContext, *permissions) - Timber.d("isOverQuota: $isOverQuota, hasStoragePermission: $hasReadPermission, isRunning: ${cameraUploadsServiceWrapper.isServiceRunning()}, ignoreAttributes: $ignoreAttributes") - if (!cameraUploadsServiceWrapper.isServiceRunning() && !isOverQuota && hasReadPermission) { + val permissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + arrayOf( + getNotificationsPermission(), + getImagePermissionByVersion(), + getVideoPermissionByVersion() + ) + } else { + arrayOf( + getImagePermissionByVersion(), + getVideoPermissionByVersion() + ) + } + val hasMediaPermissions = permissionUtilWrapper.hasPermissions(appContext, *permissions) + Timber.d("isOverQuota: $isOverQuota, hasMediaPermissions: $hasMediaPermissions, isRunning: ${cameraUploadsServiceWrapper.isServiceRunning()}, ignoreAttributes: $ignoreAttributes") + if (!cameraUploadsServiceWrapper.isServiceRunning() && !isOverQuota && hasMediaPermissions) { val newIntent = Intent(appContext, CameraUploadsService::class.java) newIntent.putExtra(EXTRA_IGNORE_ATTR_CHECK, ignoreAttributes) newIntent.putExtra(EXTRA_PRIMARY_SYNC_SUCCESS, isPrimaryHandleSynced) diff --git a/app/src/main/java/mega/privacy/android/app/listeners/GetCameraUploadAttributeListener.kt b/app/src/main/java/mega/privacy/android/app/listeners/GetCameraUploadAttributeListener.kt index 20f57cfb581..34b36e1f860 100644 --- a/app/src/main/java/mega/privacy/android/app/listeners/GetCameraUploadAttributeListener.kt +++ b/app/src/main/java/mega/privacy/android/app/listeners/GetCameraUploadAttributeListener.kt @@ -60,6 +60,7 @@ class GetCameraUploadAttributeListener(val context: Context?) : MegaRequestListe if (request.type == MegaRequest.TYPE_GET_ATTR_USER || request.paramType == MegaApiJava.USER_ATTR_CAMERA_UPLOADS_FOLDER ) { + Timber.d("onRequestFinish errorCode: ${error.errorCode}") when (error.errorCode) { MegaError.API_OK -> { val handles = getCUHandles(request) @@ -72,7 +73,7 @@ class GetCameraUploadAttributeListener(val context: Context?) : MegaRequestListe } MegaError.API_ENOENT -> { // Happens when both Primary and Secondary folders are not set - Timber.d("First time setting Camera Upload Attributes") + Timber.w("First time setting Camera Upload Attributes") CameraUploadUtil.initCUFolderFromScratch(context, false) // TODO Move call here after this class is use case (context as? CameraUploadsService)?.onGetPrimaryFolderAttribute( @@ -81,7 +82,7 @@ class GetCameraUploadAttributeListener(val context: Context?) : MegaRequestListe ) } else -> { - Timber.w("Get Camera Upload Attributes FAIL, Error Code: ${error.errorCode}, ${error.errorString}") + Timber.e("Get Camera Upload Attributes Failed, Error Code: ${error.errorCode}, ${error.errorString}. Stopping Camera Uploads") JobUtil.fireStopCameraUploadJob(context) } } @@ -119,28 +120,33 @@ class GetCameraUploadAttributeListener(val context: Context?) : MegaRequestListe * @param isSecondary Is the handle Secondary */ private fun handle(handle: Long, isSecondary: Boolean) { + Timber.d("Handle: $handle, isSecondary = $isSecondary") var shouldStopCameraUpload = false if (isNodeInRubbishOrDeleted(handle)) { - Timber.d("Folder in rubbish bin, is secondary: %s", isSecondary) + Timber.w("Folder in rubbish bin, is secondary: %s", isSecondary) CameraUploadUtil.initCUFolderFromScratch(context, isSecondary) } else { shouldStopCameraUpload = CameraUploadUtil.compareAndUpdateLocalFolderAttribute(handle, isSecondary) // stop CameraUpload if destination has changed if (shouldStopCameraUpload) { + Timber.e("Camera Uploads folder destination has changed. Stopping Camera Uploads") JobUtil.fireStopCameraUploadJob(context) } CameraUploadUtil.forceUpdateCameraUploadFolderIcon(isSecondary, handle) } if (!shouldStopCameraUpload) { + Timber.d("Camera Uploads should not be stopped") if (!isSecondary) { // Primary is set first, camera upload will not get started, do not call a start service worker // TODO Move call here after this class is use case + Timber.d("Call onGetPrimaryFolderAttribute with handle: $handle") (context as? CameraUploadsService)?.onGetPrimaryFolderAttribute(handle, false) } else { // Secondary is set after primary, camera upload will get started // TODO pass isPrimaryHandleSynced = true as intent extra and call start service worker // TODO Move call here after this class is use case + Timber.d("Call onGetSecondaryFolderAttribute with handle $handle") (context as? CameraUploadsService)?.onGetSecondaryFolderAttribute(handle) } } diff --git a/app/src/main/java/mega/privacy/android/app/utils/CameraUploadUtil.java b/app/src/main/java/mega/privacy/android/app/utils/CameraUploadUtil.java index e1cc97d5430..58cbf043a40 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/CameraUploadUtil.java +++ b/app/src/main/java/mega/privacy/android/app/utils/CameraUploadUtil.java @@ -2,7 +2,6 @@ import static mega.privacy.android.app.utils.Constants.SEPARATOR; import static mega.privacy.android.app.utils.FileUtil.purgeDirectory; -import static mega.privacy.android.app.utils.JobUtil.fireStopCameraUploadJob; import static mega.privacy.android.app.utils.StringResourcesUtils.getString; import static mega.privacy.android.app.utils.TextUtil.isTextEmpty; import static mega.privacy.android.data.facade.CameraUploadMediaFacadeKt.BROADCAST_ACTION_INTENT_CU_ATTR_CHANGE; @@ -175,13 +174,11 @@ public static void disableCameraUploadSettingProcess(boolean clearCamsyncRecords // disable both primary and secondary. dbH.setCamSyncEnabled(false); dbH.setSecondaryUploadEnabled(false); - fireStopCameraUploadJob(app); } public static void disableMediaUploadProcess() { resetMUTimestampsAndCache(); dbH.setSecondaryUploadEnabled(false); - fireStopCameraUploadJob(app); } /** diff --git a/app/src/main/java/mega/privacy/android/app/utils/JobUtil.java b/app/src/main/java/mega/privacy/android/app/utils/JobUtil.java index 82b753bc722..bd0fa3b36da 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/JobUtil.java +++ b/app/src/main/java/mega/privacy/android/app/utils/JobUtil.java @@ -62,8 +62,6 @@ public class JobUtil { private static final String CANCEL_UPLOADS_TAG = "CANCEL_UPLOADS_TAG"; - private static final String HANDLE_ATTRIBUTES_TAG = "HANDLE_ATTRIBUTES_TAG"; - /** * Schedule job of camera upload * @@ -208,6 +206,32 @@ public static void rescheduleCameraUpload(final Context context) { }, CU_RESCHEDULE_INTERVAL); } + /** + * Restart Camera Uploads by executing {@link StopCameraUploadWorker} and {@link StartCameraUploadWorker} + * sequentially through Work Chaining + * + * @param context The Context to enqueue work + * @param shouldIgnoreAttributes Whether to start Camera Uploads without checking User Attributes + */ + public static synchronized void fireRestartCameraUploadJob(Context context, Boolean shouldIgnoreAttributes) { + OneTimeWorkRequest stopCameraUploadRequest = new OneTimeWorkRequest.Builder(StopCameraUploadWorker.class) + .addTag(STOP_CAMERA_UPLOAD_TAG) + .build(); + OneTimeWorkRequest startCameraUploadRequest = new OneTimeWorkRequest.Builder(StartCameraUploadWorker.class) + .addTag(SINGLE_CAMERA_UPLOAD_TAG) + .setInputData(new Data.Builder() + .putBoolean(SHOULD_IGNORE_ATTRIBUTES, shouldIgnoreAttributes) + .putBoolean(IS_PRIMARY_HANDLE_SYNC_DONE, false) + .build() + ) + .build(); + + WorkManager.getInstance(context) + .beginWith(stopCameraUploadRequest) + .then(startCameraUploadRequest) + .enqueue(); + } + /** * Stop the camera upload work by tag. * Stop regular camera upload sync heartbeat work by tag. diff --git a/app/src/main/java/mega/privacy/android/app/utils/wrapper/JobUtilWrapper.kt b/app/src/main/java/mega/privacy/android/app/utils/wrapper/JobUtilWrapper.kt index 2b386fe611b..edd3224c343 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/wrapper/JobUtilWrapper.kt +++ b/app/src/main/java/mega/privacy/android/app/utils/wrapper/JobUtilWrapper.kt @@ -8,12 +8,39 @@ import mega.privacy.android.app.utils.JobUtil */ interface JobUtilWrapper { + /** + * Wrapper method that calls [JobUtil.isOverQuota] + * + * @return Whether the account is Over quota + */ fun isOverQuota(): Boolean = JobUtil.isOverQuota() + /** + * Wrapper method that calls [JobUtil.fireCameraUploadJob] + * + * @param context [Context] + * @param shouldIgnoreAttributes Whether to start Camera Uploads without checking User Attributes + * + * @return an Integer value + */ fun fireCameraUploadJob(context: Context, shouldIgnoreAttributes: Boolean): Int = JobUtil.fireCameraUploadJob(context, shouldIgnoreAttributes) + /** + * Wrapper method that calls [JobUtil.fireStopCameraUploadJob] + * + * @param context [Context] + */ fun fireStopCameraUploadJob(context: Context) = JobUtil.fireStopCameraUploadJob(context) + /** + * Wrapper method that calls [JobUtil.fireRestartCameraUploadJob] + * + * @param context [Context] + * @param shouldIgnoreAttributes Whether to start Camera Uploads without checking User Attributes + */ + fun fireRestartCameraUploadJob(context: Context, shouldIgnoreAttributes: Boolean) = + JobUtil.fireRestartCameraUploadJob(context, shouldIgnoreAttributes) + } diff --git a/app/src/test/java/test/mega/privacy/android/app/di/TestCameraUploadUseCases.kt b/app/src/test/java/test/mega/privacy/android/app/di/TestCameraUploadUseCases.kt index 2f16e26435a..9cd8caf8867 100644 --- a/app/src/test/java/test/mega/privacy/android/app/di/TestCameraUploadUseCases.kt +++ b/app/src/test/java/test/mega/privacy/android/app/di/TestCameraUploadUseCases.kt @@ -72,6 +72,7 @@ import mega.privacy.android.domain.usecase.ResetCameraUploadTimelines import mega.privacy.android.domain.usecase.ResetMediaUploadTimeStamps import mega.privacy.android.domain.usecase.ResetPrimaryTimeline import mega.privacy.android.domain.usecase.ResetSecondaryTimeline +import mega.privacy.android.domain.usecase.RestartCameraUpload import mega.privacy.android.domain.usecase.RestorePrimaryTimestamps import mega.privacy.android.domain.usecase.RestoreSecondaryTimestamps import mega.privacy.android.domain.usecase.SaveSyncRecord @@ -101,6 +102,9 @@ object TestCameraUploadUseCases { @Provides fun provideStopCameraUpload() = mock() + @Provides + fun provideRestartCameraUpload() = mock() + @Provides fun provideHasCredentials() = mock() diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultSetupPrimaryFolder.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultSetupPrimaryFolder.kt index c9587e180b8..d995d2f0444 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultSetupPrimaryFolder.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultSetupPrimaryFolder.kt @@ -8,8 +8,8 @@ import javax.inject.Inject */ class DefaultSetupPrimaryFolder @Inject constructor( private val cameraUploadRepository: CameraUploadRepository, - private val startCameraUpload: StartCameraUpload, private val stopCameraUpload: StopCameraUpload, + private val restartCameraUpload: RestartCameraUpload, private val resetPrimaryTimeline: ResetPrimaryTimeline, private val updateFolderIconBroadcast: UpdateFolderIconBroadcast, private val updateFolderDestinationBroadcast: UpdateFolderDestinationBroadcast, @@ -23,8 +23,7 @@ class DefaultSetupPrimaryFolder @Inject constructor( cameraUploadRepository.setPrimaryFolderHandle(newPrimaryHandle) cameraUploadRepository.setPrimarySyncHandle(newPrimaryHandle) updateFolderIconBroadcast(newPrimaryHandle, false) - stopCameraUpload() - startCameraUpload(true) + restartCameraUpload(shouldIgnoreAttributes = true) updateFolderDestinationBroadcast(newPrimaryHandle, false) } }.onFailure { diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/RestartCameraUpload.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/RestartCameraUpload.kt new file mode 100644 index 00000000000..885497eaa30 --- /dev/null +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/RestartCameraUpload.kt @@ -0,0 +1,14 @@ +package mega.privacy.android.domain.usecase + +/** + * Use case to restart Camera Uploads + */ +fun interface RestartCameraUpload { + + /** + * Invocation method + * + * @param shouldIgnoreAttributes Whether to start Camera Uploads without checking User Attributes + */ + suspend operator fun invoke(shouldIgnoreAttributes: Boolean) +} \ No newline at end of file diff --git a/domain/src/test/kotlin/mega/privacy/android/domain/usecase/DefaultSetupPrimaryFolderTest.kt b/domain/src/test/kotlin/mega/privacy/android/domain/usecase/DefaultSetupPrimaryFolderTest.kt index 3402c32fae2..4fd00307a89 100644 --- a/domain/src/test/kotlin/mega/privacy/android/domain/usecase/DefaultSetupPrimaryFolderTest.kt +++ b/domain/src/test/kotlin/mega/privacy/android/domain/usecase/DefaultSetupPrimaryFolderTest.kt @@ -16,13 +16,13 @@ class DefaultSetupPrimaryFolderTest { private lateinit var underTest: SetupPrimaryFolder private val invalidHandle = -1L - private val cameraUploadRepository = mock() { + private val cameraUploadRepository = mock { onBlocking { getInvalidHandle() }.thenReturn(invalidHandle) } - private val startCameraUpload = mock() private val stopCameraUpload = mock() + private val restartCameraUpload = mock() private val resetPrimaryTimeline = mock() private val updateFolderIconBroadcast = mock() private val updateFolderDestinationBroadcast = mock() @@ -31,8 +31,8 @@ class DefaultSetupPrimaryFolderTest { fun setUp() { underTest = DefaultSetupPrimaryFolder( cameraUploadRepository = cameraUploadRepository, - startCameraUpload = startCameraUpload, stopCameraUpload = stopCameraUpload, + restartCameraUpload = restartCameraUpload, resetPrimaryTimeline = resetPrimaryTimeline, updateFolderIconBroadcast = updateFolderIconBroadcast, updateFolderDestinationBroadcast = updateFolderDestinationBroadcast @@ -49,8 +49,7 @@ class DefaultSetupPrimaryFolderTest { verify(cameraUploadRepository).setPrimaryFolderHandle(result) verify(cameraUploadRepository).setPrimarySyncHandle(result) verify(updateFolderIconBroadcast).invoke(result, false) - verify(stopCameraUpload).invoke() - verify(startCameraUpload).invoke(true) + verify(restartCameraUpload).invoke(shouldIgnoreAttributes = true) verify(updateFolderDestinationBroadcast).invoke(result, false) } From 8f70596f191187b68212983f51b78383d5cff581 Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Fri, 27 Jan 2023 10:08:03 +0800 Subject: [PATCH 029/334] CC-3126/T4353052 AND - Play video from where user last exited with dialog The MR link: https://code.developers.mega.co.nz/mobile/android/android/-/merge_requests/4172 --- .../gateway/PlayerServiceViewModelGateway.kt | 7 ++++ .../mediaplayer/service/MediaPlayerService.kt | 34 ++++++++++++------- .../service/MediaPlayerServiceViewModel.kt | 9 +++-- .../android/domain/di/MediaPlayerUseCases.kt | 8 +++++ .../usecase/DeletePlaybackInformation.kt | 14 ++++++++ 5 files changed, 56 insertions(+), 16 deletions(-) create mode 100644 domain/src/main/kotlin/mega/privacy/android/domain/usecase/DeletePlaybackInformation.kt diff --git a/app/src/main/java/mega/privacy/android/app/mediaplayer/gateway/PlayerServiceViewModelGateway.kt b/app/src/main/java/mega/privacy/android/app/mediaplayer/gateway/PlayerServiceViewModelGateway.kt index 7dee41dd23b..de3e3e9ff24 100644 --- a/app/src/main/java/mega/privacy/android/app/mediaplayer/gateway/PlayerServiceViewModelGateway.kt +++ b/app/src/main/java/mega/privacy/android/app/mediaplayer/gateway/PlayerServiceViewModelGateway.kt @@ -348,6 +348,13 @@ interface PlayerServiceViewModelGateway { */ suspend fun savePlaybackTimes() + /** + * Delete playback information + * + * @param mediaId the media id of deleted item + */ + suspend fun deletePlaybackInformation(mediaId: Long) + /** * Monitor playback times * diff --git a/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerService.kt b/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerService.kt index 2e2b4123ffb..797860b3190 100644 --- a/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerService.kt +++ b/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerService.kt @@ -150,8 +150,13 @@ abstract class MediaPlayerService : LifecycleService(), LifecycleEventObserver, positionInMs?.let { position -> mediaPlayerGateway.playerSeekToPositionInMs(position) } - VIDEO_TYPE_SHOW_PLAYBACK_POSITION_DIALOG, - -> { + VIDEO_TYPE_RESTART_PLAYBACK_POSITION -> { + // Remove current playback history, if video type is restart + lifecycleScope.launch { + viewModelGateway.deletePlaybackInformation(it.toLong()) + } + } + VIDEO_TYPE_SHOW_PLAYBACK_POSITION_DIALOG -> { // Detect the media item whether is transition by comparing // currentPlayingHandle if is parameter handle if (currentPlayingHandle != it.toLong() @@ -172,6 +177,10 @@ abstract class MediaPlayerService : LifecycleService(), LifecycleEventObserver, // If currentPlayHandle is parameter handle and there is // no playback history, the video is playing after ready isPlayingAfterReady = true + // Set playWhenReady to be true to ensure the video is playing after ready + if (!mediaPlayerGateway.getPlayWhenReady()) { + setPlayWhenReady(true) + } } } } @@ -552,15 +561,19 @@ abstract class MediaPlayerService : LifecycleService(), LifecycleEventObserver, override fun playbackPositionStateUpdate() = showPlaybackPositionDialogUpdate override fun setRestartPlayVideo() { - updateDialogShownStateAndVideoPlayType(VIDEO_PLAY_TYPE_DEFAULT) + updateDialogShownStateAndVideoPlayType(VIDEO_TYPE_RESTART_PLAYBACK_POSITION) // Set playWhenReady to be true, making the video is playing after the restart button is clicked if (!mediaPlayerGateway.getPlayWhenReady()) { setPlayWhenReady(true) } + // If the restart button is clicked, remove playback information of current item + lifecycleScope.launch { + viewModelGateway.deletePlaybackInformation(viewModelGateway.getCurrentPlayingHandle()) + } } override fun setRestartPlayVideoBeforeBuildSources() { - updateDialogShownStateAndVideoPlayType(VIDEO_PLAY_TYPE_DEFAULT) + updateDialogShownStateAndVideoPlayType(VIDEO_TYPE_RESTART_PLAYBACK_POSITION) // Initial video sources after the restart button is clicked initVideoSources(mediaPlayerIntent) } @@ -585,18 +598,13 @@ abstract class MediaPlayerService : LifecycleService(), LifecycleEventObserver, override fun cancelPlaybackPositionDialog() { updateDialogShownStateAndVideoPlayType(VIDEO_TYPE_SHOW_PLAYBACK_POSITION_DIALOG) - // Set isPlayingAfterReady to be true, making the video is playing after dialog is dismissed - isPlayingAfterReady = true - // Set playWhenReady to be true, making the video is playing after the video has been paused - if (!mediaPlayerGateway.getPlayWhenReady()) { - setPlayWhenReady(true) - } } override fun cancelPlaybackPositionDialogBeforeBuildSources() { updateDialogShownStateAndVideoPlayType(VIDEO_TYPE_SHOW_PLAYBACK_POSITION_DIALOG) - isPlayingAfterReady = true initVideoSources(mediaPlayerIntent) + // If the dialog is cancelled, set PlayWhenReady to be false to paused video after build sources. + setPlayWhenReady(false) } /** @@ -627,8 +635,8 @@ abstract class MediaPlayerService : LifecycleService(), LifecycleEventObserver, private const val RESUME_DELAY_MS = 500L private const val VIDEO_TYPE_RESUME_PLAYBACK_POSITION = 123 - private const val VIDEO_TYPE_SHOW_PLAYBACK_POSITION_DIALOG = 124 - private const val VIDEO_PLAY_TYPE_DEFAULT = 125 + private const val VIDEO_TYPE_RESTART_PLAYBACK_POSITION = 124 + private const val VIDEO_TYPE_SHOW_PLAYBACK_POSITION_DIALOG = 125 /** * The minimum size of single playlist diff --git a/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerServiceViewModel.kt b/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerServiceViewModel.kt index c540315272f..b45e640a39a 100644 --- a/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerServiceViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerServiceViewModel.kt @@ -96,6 +96,7 @@ import mega.privacy.android.domain.entity.node.TypedNode import mega.privacy.android.domain.qualifier.ApplicationScope import mega.privacy.android.domain.qualifier.IoDispatcher import mega.privacy.android.domain.usecase.AreCredentialsNull +import mega.privacy.android.domain.usecase.DeletePlaybackInformation import mega.privacy.android.domain.usecase.GetAudioNodes import mega.privacy.android.domain.usecase.GetAudioNodesByEmail import mega.privacy.android.domain.usecase.GetAudioNodesByParentHandle @@ -162,6 +163,7 @@ class MediaPlayerServiceViewModel @Inject constructor( private val trackPlaybackPositionUseCase: TrackPlaybackPosition, private val monitorPlaybackTimesUseCase: MonitorPlaybackTimes, private val savePlaybackTimesUseCase: SavePlaybackTimes, + private val deletePlaybackInformationUseCase: DeletePlaybackInformation, private val megaApiFolderHttpServerSetMaxBufferSize: MegaApiFolderHttpServerSetMaxBufferSize, private val megaApiFolderHttpServerIsRunning: MegaApiFolderHttpServerIsRunning, private val megaApiFolderHttpServerStart: MegaApiFolderHttpServerStart, @@ -1260,9 +1262,10 @@ class MediaPlayerServiceViewModel @Inject constructor( } } - override suspend fun savePlaybackTimes() { - savePlaybackTimesUseCase() - } + override suspend fun savePlaybackTimes() = savePlaybackTimesUseCase() + + override suspend fun deletePlaybackInformation(mediaId: Long) = + deletePlaybackInformationUseCase(mediaId) override fun updateItemName(handle: Long, newName: String) = playlistItemsFlow.update { diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/di/MediaPlayerUseCases.kt b/domain/src/main/kotlin/mega/privacy/android/domain/di/MediaPlayerUseCases.kt index aed9659e6c2..97fc2315d17 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/di/MediaPlayerUseCases.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/di/MediaPlayerUseCases.kt @@ -24,6 +24,7 @@ import mega.privacy.android.domain.usecase.DefaultGetVideoNodesFromOutShares import mega.privacy.android.domain.usecase.DefaultGetVideoNodesFromPublicLinks import mega.privacy.android.domain.usecase.DefaultGetVideosByParentHandleFromMegaApiFolder import mega.privacy.android.domain.usecase.DefaultTrackPlaybackPosition +import mega.privacy.android.domain.usecase.DeletePlaybackInformation import mega.privacy.android.domain.usecase.GetAudioNodes import mega.privacy.android.domain.usecase.GetAudioNodesByEmail import mega.privacy.android.domain.usecase.GetAudioNodesByParentHandle @@ -195,6 +196,13 @@ abstract class MediaPlayerUseCases { fun provideSavePlaybackTimes(mediaPlayerRepository: MediaPlayerRepository): SavePlaybackTimes = SavePlaybackTimes(mediaPlayerRepository::savePlaybackTimes) + /** + * Provide implementation for [DeletePlaybackInformation] + */ + @Provides + fun provideDeletePlaybackInformation(mediaPlayerRepository: MediaPlayerRepository): DeletePlaybackInformation = + DeletePlaybackInformation(mediaPlayerRepository::deletePlaybackInformation) + /** * Provide implementation for [GetLocalFolderLinkFromMegaApiFolder] */ diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DeletePlaybackInformation.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DeletePlaybackInformation.kt new file mode 100644 index 00000000000..9f7eb49bcaa --- /dev/null +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DeletePlaybackInformation.kt @@ -0,0 +1,14 @@ +package mega.privacy.android.domain.usecase + +/** + * The use case for deleting playback information + */ +fun interface DeletePlaybackInformation { + + /** + * Delete playback information + * + * @param mediaId the media id of deleted item + */ + suspend operator fun invoke(mediaId: Long) +} \ No newline at end of file From de92e45e966003bfb22e385ff56f7b8022340958 Mon Sep 17 00:00:00 2001 From: Robin Shi Date: Fri, 10 Feb 2023 21:48:22 +1300 Subject: [PATCH 030/334] AND-15634 use prebuilt SDK commit in Google release (cherry picked from commit 105119b6f10ff9b49f8cbcb475a0ff2336b34dc9) --- app/build.gradle | 10 ++++++++++ jenkinsfile/android_release.groovy | 7 ++++--- jenkinsfile/common.groovy | 5 ++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 08080a44855..d36ee60e22c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -560,6 +560,16 @@ task printAppVersionName { } } +/** + * Gradle task for getting the pre-build SDK version + * Run ./gradlew -q printPrebuildSdkVersion + */ +task printPrebuildSdkVersion { + doLast { + println megaSdkVersion + } +} + /** * Gradle task for getting the app version name channel * Run ./gradlew -q printAppVersionNameChannel diff --git a/jenkinsfile/android_release.groovy b/jenkinsfile/android_release.groovy index 389e9a58e36..137ab0b36d1 100644 --- a/jenkinsfile/android_release.groovy +++ b/jenkinsfile/android_release.groovy @@ -478,16 +478,17 @@ private String releaseSuccessMessage(String lineBreak, Object common) { * @return the composed message */ private String getBuildVersionInfo(Object common) { - + println("entering getBuildVersionInfo") String artifactoryUrl = "${env.ARTIFACTORY_BASE_URL}/artifactory/android-mega/release/${common.artifactoryUploadPath()}" String artifactVersion = common.readAppVersion2() String gmsAabUrl = "${artifactoryUrl}/${artifactVersion}-gms-release.aab" String gmsApkUrl = "${artifactoryUrl}/${artifactVersion}-gms-release.apk" + String sdkVersion = common.readPrebuiltSdkVersion() String appCommitLink = "${env.GITLAB_BASE_URL}/mobile/android/android/-/commit/" + common.appCommitId() - String sdkCommitLink = "${env.GITLAB_BASE_URL}/sdk/sdk/-/commit/" + common.sdkCommitId() - String chatCommitLink = "${env.GITLAB_BASE_URL}/megachat/MEGAchat/-/commit/" + common.megaChatSdkCommitId() + String sdkCommitLink = "${env.GITLAB_BASE_URL}/sdk/sdk/-/commit/" + common.queryPrebuiltSdkProperty("sdk-commit", sdkVersion) + String chatCommitLink = "${env.GITLAB_BASE_URL}/megachat/MEGAchat/-/commit/" + common.queryPrebuiltSdkProperty("chat-commit", sdkVersion) String appBranch = env.gitlabSourceBranch diff --git a/jenkinsfile/common.groovy b/jenkinsfile/common.groovy index f871dcc4247..d326eed3bfa 100644 --- a/jenkinsfile/common.groovy +++ b/jenkinsfile/common.groovy @@ -166,7 +166,9 @@ void checkoutMegaChatSdkByTag(String megaChatTag) { * @return version of prebuilt SDK */ String readPrebuiltSdkVersion() { - return sh(script: "grep megaSdkVersion build.gradle | awk -F= '{print \$2}'", returnStdout: true).trim().replaceAll("\"", "") + String version = sh(script: "./gradlew -q printPrebuildSdkVersion | tail -n 1", returnStdout: true).trim() + println("readPrebuiltSdkVersion version = $version") + return version } /** @@ -592,4 +594,5 @@ void downloadAndExtractNativeSymbols() { } + return this From bb40f2b8d01499b433bb6050a051f024daf9d42f Mon Sep 17 00:00:00 2001 From: Gregg Meyrick Jover Date: Mon, 13 Feb 2023 18:43:04 +1300 Subject: [PATCH 031/334] AND-15169: Fix Switch View Icon Not Changing Properly in Shares Section This aims to fix the TestRail issue T4413398. Changelog: - Initialize sortByHeaderViewModel with by activityViewModels in MegaNodeBaseFragment to persist the View Type state until ManagerActivity gets destroyed. - Move showSortByPanel() observation to onViewCreated(). This also applies to observers in individual Shares Fragments. - Remove Build Warnings on spacing issues. --- .../shares/MegaNodeBaseFragment.kt | 103 +++++++++++------- .../shares/incoming/IncomingSharesFragment.kt | 37 +++++-- .../shares/links/LinksFragment.kt | 43 ++++++-- .../shares/outgoing/OutgoingSharesFragment.kt | 35 ++++-- 4 files changed, 148 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/MegaNodeBaseFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/MegaNodeBaseFragment.kt index 750a4b0ea81..d5334d95212 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/MegaNodeBaseFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/MegaNodeBaseFragment.kt @@ -17,7 +17,7 @@ import android.widget.Toast import androidx.appcompat.view.ActionMode import androidx.core.content.FileProvider import androidx.core.text.HtmlCompat -import androidx.fragment.app.viewModels +import androidx.fragment.app.activityViewModels import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -84,7 +84,7 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { /** * viewModel responsible for sorting the list */ - protected val sortByHeaderViewModel by viewModels() + protected val sortByHeaderViewModel by activityViewModels() /** * Adapter holding the list of nodes @@ -152,21 +152,11 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { */ protected abstract fun showSortByPanel() - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?, - ): View? { - sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, EventObserver { - showSortByPanel() - }) - return super.onCreateView(inflater, container, savedInstanceState) - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) recyclerView?.let { observeDragSupportEvents(viewLifecycleOwner, it, viewerFrom) } checkScroll() + setupObservers() } override fun onAttach(context: Context) { @@ -282,6 +272,15 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { View.VISIBLE } + /** + * Setup ViewModel observers + */ + private fun setupObservers() { + sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, EventObserver { + showSortByPanel() + }) + } + /** * Display the elevation of the app bar or not */ @@ -357,9 +356,11 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { val mediaFile = File(localPath) setDataAndType( - FileProvider.getUriForFile(requireContext(), + FileProvider.getUriForFile( + requireContext(), Constants.AUTHORITY_STRING_FILE_PROVIDER, - mediaFile), + mediaFile + ), MimeTypeList.typeForName(node.name).type ) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) @@ -392,9 +393,11 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { setDataAndType(it, mimeTypeType) } ?: run { Timber.e("ERROR:httpServerGetLocalLink") - managerActivity?.showSnackbar(Constants.SNACKBAR_TYPE, + managerActivity?.showSnackbar( + Constants.SNACKBAR_TYPE, getString(R.string.general_text_error), - MegaApiJava.INVALID_HANDLE) + MegaApiJava.INVALID_HANDLE + ) return } } @@ -423,9 +426,11 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { if (localPath != null) { val mediaFile = File(localPath) setDataAndType( - FileProvider.getUriForFile(requireContext(), + FileProvider.getUriForFile( + requireContext(), Constants.AUTHORITY_STRING_FILE_PROVIDER, - mediaFile), + mediaFile + ), mimeTypeType ) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) @@ -451,9 +456,11 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { setDataAndType(it, mimeTypeType) } ?: run { Timber.e("ERROR:httpServerGetLocalLink") - managerActivity?.showSnackbar(Constants.SNACKBAR_TYPE, + managerActivity?.showSnackbar( + Constants.SNACKBAR_TYPE, getString(R.string.general_text_error), - MegaApiJava.INVALID_HANDLE) + MegaApiJava.INVALID_HANDLE + ) return } } @@ -469,7 +476,8 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { else -> { Timber.d("itemClick:isFile:otherOption") managerActivity?.let { - onNodeTapped(requireActivity(), + onNodeTapped( + requireActivity(), node, { node: MegaNode? -> it.saveNodeByTap(node) }, it, @@ -490,17 +498,21 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { private fun launchIntent(intent: Intent?, internalIntent: Boolean, position: Int) { if (intent != null) { if (internalIntent || MegaApiUtils.isIntentAvailable(requireContext(), intent)) { - putThumbnailLocation(intent, + putThumbnailLocation( + intent, recyclerView ?: return, position, viewerFrom, - adapter ?: return) + adapter ?: return + ) startActivity(intent) managerActivity?.overridePendingTransition(0, 0) } else { - Toast.makeText(requireContext(), + Toast.makeText( + requireContext(), StringResourcesUtils.getString(R.string.intent_not_available), - Toast.LENGTH_LONG).show() + Toast.LENGTH_LONG + ).show() } } } @@ -513,8 +525,12 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { recyclerView = v.findViewById(R.id.file_list_view_browser) mLayoutManager = LinearLayoutManager(requireContext()) recyclerView?.layoutManager = mLayoutManager - recyclerView?.addItemDecoration(PositionDividerItemDecoration(requireContext(), - resources.displayMetrics)) + recyclerView?.addItemDecoration( + PositionDividerItemDecoration( + requireContext(), + resources.displayMetrics + ) + ) fastScroller = v.findViewById(R.id.fastscroll) setRecyclerView() recyclerView?.itemAnimator = Util.noChangeRecyclerViewItemAnimator() @@ -548,10 +564,12 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { * Setup the recyclerview */ private fun setRecyclerView() { - recyclerView?.setPadding(0, + recyclerView?.setPadding( + 0, 0, 0, - Util.dp2px(85.toFloat(), resources.displayMetrics)) + Util.dp2px(85.toFloat(), resources.displayMetrics) + ) recyclerView?.setHasFixedSize(true) recyclerView?.clipToPadding = false recyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() { @@ -581,13 +599,17 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { } try { - text = text.replace("[A]", "") + text = text.replace( + "[A]", "" + ) text = text.replace("[/A]", "") - text = text.replace("[B]", "") + text = text.replace( + "[B]", "" + ) text = text.replace("[/B]", "") } catch (e: Exception) { Timber.w(e, "Exception formatting text") @@ -665,8 +687,10 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { managerActivity?.showConfirmationRemoveSeveralPublicLinks(nodes) hideActionMode() } - R.id.cab_menu_leave_share -> showConfirmationLeaveIncomingShares(requireActivity(), - (requireActivity() as SnackbarShower), handleList) + R.id.cab_menu_leave_share -> showConfirmationLeaveIncomingShares( + requireActivity(), + (requireActivity() as SnackbarShower), handleList + ) R.id.cab_menu_send_to_chat -> { adapter?.arrayListSelectedNodes?.let { managerActivity?.attachNodesToChats(it) @@ -677,7 +701,8 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { R.id.cab_menu_select_all -> selectAll() R.id.cab_menu_clear_selection -> hideActionMode() R.id.cab_menu_remove_share -> managerActivity?.showConfirmationRemoveAllSharingContacts( - selected) + selected + ) } return true } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index edb7ab9f987..b7dbbb98b01 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -43,10 +43,13 @@ import java.util.Collections @AndroidEntryPoint class IncomingSharesFragment : MegaNodeBaseFragment() { - private val viewModel: IncomingSharesViewModel by activityViewModels() + private val viewModel by activityViewModels() private fun state() = viewModel.state.value + /** + * onCreateView + */ override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -62,12 +65,22 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { else getGridView(inflater, container) initAdapter() - observe() selectNewlyAddedNodes() return view } + /** + * onViewCreated + */ + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupObservers() + } + + /** + * activateActionMode + */ override fun activateActionMode() { if (adapter?.isMultipleSelect == true) return @@ -202,9 +215,9 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { get() = state().incomingHandle /** - * Observe viewModel + * Setup ViewModel observers */ - private fun observe() { + private fun setupObservers() { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { viewModel.state.collect { @@ -227,7 +240,6 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) - } } } @@ -313,8 +325,10 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { if (isInvalidHandle) { emptyImageView?.let { - setImageViewAlphaIfDark(requireContext(), - it, ColorUtils.DARK_IMAGE_ALPHA) + setImageViewAlphaIfDark( + requireContext(), + it, ColorUtils.DARK_IMAGE_ALPHA + ) } if (Util.isScreenInPortrait(requireContext())) { emptyImageView?.setImageResource(R.drawable.incoming_shares_empty) @@ -338,8 +352,10 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { } if (selected.size == 1 - && megaApi.checkAccessErrorExtended(selected[0], - MegaShare.ACCESS_FULL).errorCode == MegaError.API_OK + && megaApi.checkAccessErrorExtended( + selected[0], + MegaShare.ACCESS_FULL + ).errorCode == MegaError.API_OK ) { control.rename().isVisible = true if (control.alwaysActionCount() < CloudStorageOptionControlUtil.MAX_ACTION_COUNT) { @@ -350,7 +366,8 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { } if (state().incomingTreeDepth > 0 && selected.isNotEmpty() && allHaveFullAccess( - selected) + selected + ) ) { control.move().isVisible = true if (control.alwaysActionCount() < CloudStorageOptionControlUtil.MAX_ACTION_COUNT) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt index d75e06bc4b5..46dcfbed536 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt @@ -39,10 +39,13 @@ import timber.log.Timber @AndroidEntryPoint class LinksFragment : MegaNodeBaseFragment() { - private val viewModel: LinksViewModel by activityViewModels() + private val viewModel by activityViewModels() private fun state() = viewModel.state.value + /** + * onCreateView + */ override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -57,11 +60,22 @@ class LinksFragment : MegaNodeBaseFragment() { val view = getListView(inflater, container) initAdapter() - observe() return view } + /** + * onViewCreated + */ + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupObservers() + } + + /** + * activateActionMode + */ override fun activateActionMode() { if (adapter?.isMultipleSelect == true) return @@ -151,7 +165,7 @@ class LinksFragment : MegaNodeBaseFragment() { viewModel.resetLinksTreeDepth() 0 } - + } } @@ -168,9 +182,9 @@ class LinksFragment : MegaNodeBaseFragment() { get() = state().linksHandle /** - * Observe viewModel + * Setup ViewModel observers */ - private fun observe() { + private fun setupObservers() { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { viewModel.state.collect { @@ -193,7 +207,6 @@ class LinksFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) - } } } @@ -204,14 +217,16 @@ class LinksFragment : MegaNodeBaseFragment() { */ private fun initAdapter() { if (adapter == null) { - adapter = MegaNodeAdapter(requireActivity(), + adapter = MegaNodeAdapter( + requireActivity(), this, state().nodes, state().linksHandle, recyclerView, Constants.LINKS_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST, - sortByHeaderViewModel) + sortByHeaderViewModel + ) } else { adapter?.parentHandle = state().linksHandle adapter?.setListFragment(recyclerView) @@ -250,8 +265,10 @@ class LinksFragment : MegaNodeBaseFragment() { var textToShow: String? = null if (isInvalidHandle) { emptyImageView?.let { - setImageViewAlphaIfDark(requireContext(), - it, ColorUtils.DARK_IMAGE_ALPHA) + setImageViewAlphaIfDark( + requireContext(), + it, ColorUtils.DARK_IMAGE_ALPHA + ) } emptyImageView?.setImageResource(R.drawable.ic_zero_data_public_links) textToShow = requireContext().getString(R.string.context_empty_links) @@ -288,8 +305,10 @@ class LinksFragment : MegaNodeBaseFragment() { control.saveToDevice().isVisible = false } if (selected.size == 1 - && megaApi.checkAccessErrorExtended(selected[0], - MegaShare.ACCESS_FULL).errorCode == MegaError.API_OK + && megaApi.checkAccessErrorExtended( + selected[0], + MegaShare.ACCESS_FULL + ).errorCode == MegaError.API_OK ) { control.rename().isVisible = true if (control.alwaysActionCount() < CloudStorageOptionControlUtil.MAX_ACTION_COUNT) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 82dba6de539..20702cdfdb2 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -41,10 +41,13 @@ import timber.log.Timber @AndroidEntryPoint class OutgoingSharesFragment : MegaNodeBaseFragment() { - private val viewModel: OutgoingSharesViewModel by activityViewModels() + private val viewModel by activityViewModels() private fun state() = viewModel.state.value + /** + * onCreateView + */ override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -60,11 +63,22 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { else getGridView(inflater, container) initAdapter() - observe() return view } + /** + * onViewCreated + */ + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupObservers() + } + + /** + * activateActionMode + */ override fun activateActionMode() { if (adapter?.isMultipleSelect == true) return @@ -93,9 +107,11 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { // click on a file else -> - openFile(state().nodes[actualPosition], + openFile( + state().nodes[actualPosition], Constants.OUTGOING_SHARES_ADAPTER, - actualPosition) + actualPosition + ) } } @@ -193,9 +209,9 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { get() = state().outgoingHandle /** - * Observe viewModel + * Setup ViewModel observers */ - private fun observe() { + private fun setupObservers() { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { viewModel.state.collect { @@ -218,7 +234,6 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) - } } } @@ -339,8 +354,10 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { control.saveToDevice().isVisible = false } if (selected.size == 1 - && megaApi.checkAccessErrorExtended(selected[0], - MegaShare.ACCESS_FULL).errorCode == MegaError.API_OK + && megaApi.checkAccessErrorExtended( + selected[0], + MegaShare.ACCESS_FULL + ).errorCode == MegaError.API_OK ) { control.rename().isVisible = true if (control.alwaysActionCount() < CloudStorageOptionControlUtil.MAX_ACTION_COUNT) { From dce4ddf714c8ff7e74b1380faa987d78e992805f Mon Sep 17 00:00:00 2001 From: rohitsoni Date: Fri, 10 Feb 2023 12:08:48 +0530 Subject: [PATCH 032/334] AND-15407 Fixed issue folder deleted from Browser abd when we are inside same folder in FileBrowser --- .../clouddrive/FileBrowserViewModel.kt | 23 ++++++++++++++++++- .../rubbishbin/RubbishBinViewModel.kt | 2 +- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModel.kt index ff2b0e56fb0..7bf00272e67 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModel.kt @@ -14,6 +14,9 @@ import mega.privacy.android.app.domain.usecase.GetRootFolder import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates import mega.privacy.android.app.presentation.clouddrive.model.FileBrowserState import mega.privacy.android.app.presentation.settings.model.MediaDiscoveryViewSettings +import mega.privacy.android.domain.entity.node.FolderNode +import mega.privacy.android.domain.entity.node.Node +import mega.privacy.android.domain.entity.node.NodeChanges import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.MonitorMediaDiscoveryView import nz.mega.sdk.MegaApiJava @@ -80,11 +83,29 @@ class FileBrowserViewModel @Inject constructor( viewModelScope.launch { refreshNodes() monitorNodeUpdates().collect { - refreshNodes() + checkForDeletedNode(it.changes) } } } + /** + * This will update handle for fileBrowser if any node is deleted from browser and + * moved to rubbish bin + * we are in same screen else will simply refresh nodes with parentID + * @param changes [Map] of [Node], list of [NodeChanges] + */ + private fun checkForDeletedNode(changes: Map>) { + changes.forEach { (node, _) -> + if (node is FolderNode) { + if (node.isInRubbishBin && _state.value.fileBrowserHandle == node.id.longValue) { + setBrowserParentHandle(node.parentId.longValue) + return@forEach + } + } + } + refreshNodes() + } + /** * Set the current browser handle to the UI state * diff --git a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinViewModel.kt index cf6207836b8..a7ce02b1422 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinViewModel.kt @@ -132,7 +132,7 @@ class RubbishBinViewModel @Inject constructor( if (value.contains(NodeChanges.Remove) && _state.value.rubbishBinHandle == key.id.longValue) { setRubbishBinHandle(key.parentId.longValue) return@forEach - } else if (value.contains(NodeChanges.Parent)) { + } else if (value.contains(NodeChanges.Parent) && _state.value.rubbishBinHandle == key.id.longValue) { setRubbishBinHandle(-1) return@forEach } From c271b40a6debdb89cb45f730efa5129a5695a654 Mon Sep 17 00:00:00 2001 From: rohitsoni Date: Mon, 13 Feb 2023 09:58:55 +0530 Subject: [PATCH 033/334] Fixed review comments --- .../app/presentation/clouddrive/FileBrowserViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModel.kt index 7bf00272e67..f112dc9ce9b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModel.kt @@ -83,7 +83,7 @@ class FileBrowserViewModel @Inject constructor( viewModelScope.launch { refreshNodes() monitorNodeUpdates().collect { - checkForDeletedNode(it.changes) + checkForNodeIsInRubbish(it.changes) } } } @@ -93,8 +93,8 @@ class FileBrowserViewModel @Inject constructor( * moved to rubbish bin * we are in same screen else will simply refresh nodes with parentID * @param changes [Map] of [Node], list of [NodeChanges] - */ - private fun checkForDeletedNode(changes: Map>) { + `*/ + private fun checkForNodeIsInRubbish(changes: Map>) { changes.forEach { (node, _) -> if (node is FolderNode) { if (node.isInRubbishBin && _state.value.fileBrowserHandle == node.id.longValue) { From 49500e343c7c0ff84c3fbb79ec12a535f6615d2c Mon Sep 17 00:00:00 2001 From: rohitsoni Date: Mon, 13 Feb 2023 11:09:00 +0530 Subject: [PATCH 034/334] AND-13975- Search Fragment for kotlin --- .../android/app/presentation/search/SearchFragment.kt | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt index 57a8307830c..ed4ec4a833b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt @@ -142,6 +142,8 @@ class SearchFragment : RotatableFragment() { private var nodes = mutableListOf() + private lateinit var activityContext: Context + /** * [Boolean] value referenced from [managerActivity] * @@ -155,7 +157,12 @@ class SearchFragment : RotatableFragment() { * Returns instance of [ManagerActivity] from [requireActivity] */ private val managerActivity: ManagerActivity - get() = (requireActivity() as ManagerActivity) + get() = (activityContext as ManagerActivity) + + override fun onAttach(context: Context) { + super.onAttach(context) + activityContext = context + } override fun onCreateView( inflater: LayoutInflater, @@ -877,7 +884,7 @@ class SearchFragment : RotatableFragment() { return } if (nodes[position].isFolder) { - searchViewModel.setSearchParentHandle(nodes.get(position).getHandle()) + searchViewModel.setSearchParentHandle(nodes[position].handle) searchViewModel.increaseSearchDepth() var lastFirstVisiblePosition: Int From c48d24be7a98260905f3102920d8ff3bd77cc4f7 Mon Sep 17 00:00:00 2001 From: Sougandh Mp Date: Mon, 13 Feb 2023 18:27:26 +0530 Subject: [PATCH 035/334] Hotfix: AND-15447-Convert ContactInfoActivity to kotlin --- .../mega/privacy/android/app/main/ContactInfoActivity.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/mega/privacy/android/app/main/ContactInfoActivity.kt b/app/src/main/java/mega/privacy/android/app/main/ContactInfoActivity.kt index 6a52998ba9b..ef8c0812ab7 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ContactInfoActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/ContactInfoActivity.kt @@ -829,6 +829,14 @@ class ContactInfoActivity : BaseActivity(), ActionNodeCallback, MegaRequestListe } } + @Deprecated("Deprecated in Java") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (nodeSaver.handleActivityResult(this, requestCode, resultCode, data)) { + return + } + } + private fun updateViewBasedOnNetworkAvailability() { if (viewModel.isOnline()) { Timber.d("online -- network connection") From 0af590f0a623de47ea86142c696098e1df5bd84a Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Tue, 14 Feb 2023 14:21:22 +0800 Subject: [PATCH 036/334] TRAN-91 Thumbnail icon is not displaying in 'Transfer' section when user download it from link send in chat T4413438 Download from a MEGA link The MR link is !4383 (https://code.developers.mega.co.nz/mobile/android/android/-/merge_requests/4383) --- .../app/main/adapters/MegaTransfersAdapter.kt | 12 ++++++++++-- .../app/main/managerSections/TransfersFragment.kt | 11 ++++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaTransfersAdapter.kt b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaTransfersAdapter.kt index e3409959d2a..12a9c451c99 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaTransfersAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaTransfersAdapter.kt @@ -41,6 +41,7 @@ class MegaTransfersAdapter( private val selectModeInterface: SelectModeInterface, private val transfersViewModel: TransfersViewModel, private val megaApi: MegaApiAndroid, + private val megaApiFolder: MegaApiAndroid, ) : RecyclerView.Adapter(), RotatableAdapter { private var transferList: List @@ -111,10 +112,13 @@ class MegaTransfersAdapter( holder.progressText.setTextColor(ContextCompat.getColor(context, R.color.green_500_green_400)) holder.document = transfer.nodeHandle - if (!isItemChecked) { holder.iconDownloadUploadView.setImageResource(R.drawable.ic_download_transfers) - megaApi.getNodeByHandle(transfer.nodeHandle)?.let { node -> + val nodeFromMegaApi = megaApi.getNodeByHandle(transfer.nodeHandle) + val nodeFromMegaApiFolder = megaApiFolder.getNodeByHandle(transfer.nodeHandle) + // If node that gets from MegaApi is null, getting the node from megaApiFolder + val node = nodeFromMegaApi ?: nodeFromMegaApiFolder + if (node != null) { if (node.hasThumbnail()) { ThumbnailUtils.getThumbnailFromCache(node) ?: let { ThumbnailUtils.getThumbnailFromFolder(node, context) ?: let { @@ -141,6 +145,10 @@ class MegaTransfersAdapter( holder.defaultIcon.isVisible = false } ?: showDefaultIcon(holder = holder, drawableId = MimeTypeList.typeForName(transfer.fileName).iconResourceId) + } else { + // If the node cannot be got from both MegaApi and MegaApiFolder, show default icon + showDefaultIcon(holder = holder, + drawableId = MimeTypeList.typeForName(transfer.fileName).iconResourceId) } } } diff --git a/app/src/main/java/mega/privacy/android/app/main/managerSections/TransfersFragment.kt b/app/src/main/java/mega/privacy/android/app/main/managerSections/TransfersFragment.kt index 2730676d179..26d14d992e5 100644 --- a/app/src/main/java/mega/privacy/android/app/main/managerSections/TransfersFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/main/managerSections/TransfersFragment.kt @@ -34,6 +34,7 @@ import mega.privacy.android.app.utils.Util import mega.privacy.android.app.utils.Util.dp2px import mega.privacy.android.app.utils.Util.noChangeRecyclerViewItemAnimator import mega.privacy.android.data.qualifier.MegaApi +import mega.privacy.android.data.qualifier.MegaApiFolder import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaChatApiJava.MEGACHAT_INVALID_HANDLE import nz.mega.sdk.MegaError @@ -56,6 +57,13 @@ class TransfersFragment : TransfersBaseFragment(), SelectModeInterface, @MegaApi lateinit var megaApi: MegaApiAndroid + /** + * MegaApiFolder injection + */ + @Inject + @MegaApiFolder + lateinit var megaApiFolder: MegaApiAndroid + private var adapter: MegaTransfersAdapter? = null private var actionMode: ActionMode? = null @@ -91,7 +99,8 @@ class TransfersFragment : TransfersBaseFragment(), SelectModeInterface, listView = recyclerView, selectModeInterface = this, transfersViewModel = viewModel, - megaApi = megaApi) + megaApi = megaApi, + megaApiFolder = megaApiFolder) adapter?.setMultipleSelect(false) recyclerView.adapter = adapter From 99e97e8e825c04dae129bb34f1e58ff07f4f2b93 Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Wed, 15 Feb 2023 01:04:33 +1300 Subject: [PATCH 037/334] CU-219: Aggregate setCameSyncHandle with SetPrimaryFolderHandle and setMegaHandleSecondaryFolder with setSecondaryFolderHandle (Fix) --- .../android/app/jobservices/CameraUploadsService.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt b/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt index c1a0d20d613..ef8ddc0c7ea 100644 --- a/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt +++ b/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt @@ -104,7 +104,7 @@ import mega.privacy.android.domain.usecase.HasPreferences import mega.privacy.android.domain.usecase.IsCameraUploadByWifi import mega.privacy.android.domain.usecase.IsCameraUploadSyncEnabled import mega.privacy.android.domain.usecase.IsChargingRequired -import mega.privacy.android.domain.usecase.IsNodeInRubbish +import mega.privacy.android.domain.usecase.IsNodeInRubbishOrDeleted import mega.privacy.android.domain.usecase.IsSecondaryFolderEnabled import mega.privacy.android.domain.usecase.MonitorBatteryInfo import mega.privacy.android.domain.usecase.MonitorCameraUploadPauseState @@ -434,10 +434,10 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, lateinit var getSession: GetSession /** - * Is Node In Rubbish + * Is Node In Rubbish or deleted */ @Inject - lateinit var isNodeInRubbish: IsNodeInRubbish + lateinit var isNodeInRubbishOrDeleted: IsNodeInRubbishOrDeleted /** * Monitor charging stop status @@ -991,7 +991,7 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, if (primarySyncHandle == MegaApiJava.INVALID_HANDLE) { return false } - val isPrimaryFolderInRubbish = isNodeInRubbish(primarySyncHandle) + val isPrimaryFolderInRubbish = isNodeInRubbishOrDeleted(primarySyncHandle) val result = !isPrimaryFolderInRubbish || (getPrimaryFolderHandle() != MegaApiJava.INVALID_HANDLE) Timber.d("Primary Folder Established $result") @@ -1008,7 +1008,7 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback, if (secondarySyncHandle == MegaApiJava.INVALID_HANDLE) { return false } - val isSecondaryFolderInRubbish = isNodeInRubbish(getSecondaryFolderHandle()) + val isSecondaryFolderInRubbish = isNodeInRubbishOrDeleted(getSecondaryFolderHandle()) val result = !isSecondaryFolderInRubbish || (getSecondaryFolderHandle() != MegaApiJava.INVALID_HANDLE) Timber.d("Secondary Folder Established $result") From 01e9f1934735d0cfb387bf1d6c113f876d91a833 Mon Sep 17 00:00:00 2001 From: Pau Dominkovics Coll Date: Thu, 16 Feb 2023 00:39:10 +1300 Subject: [PATCH 038/334] T4413327 Convert ContactFolderSharedFragment to kotlin --- .../app/presentation/fileinfo/FileInfoActivity.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoActivity.kt b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoActivity.kt index b585528da1d..43c23e7982a 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoActivity.kt @@ -982,6 +982,17 @@ class FileInfoActivity : BaseActivity(), ActionNodeCallback, SnackbarShower { } } + /** + * Listen an propagate results to node saver. + */ + @Deprecated("Deprecated in Java") + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (nodeSaver.handleActivityResult(this, requestCode, resultCode, data)) { + return + } + } + /** * Starts a new Intent to share the folder to different contacts */ From 8cb33d5948ecea75a1ae561fbc5b32e2bb1e31e7 Mon Sep 17 00:00:00 2001 From: Rohit Soni Date: Thu, 16 Feb 2023 21:21:28 +1300 Subject: [PATCH 039/334] AND-15407 Deleting a folder from Cloud Drive from browser while inside it in app --- .../clouddrive/FileBrowserViewModel.kt | 21 ++++++++++++++----- .../clouddrive/FileBrowserViewModelTest.kt | 5 ++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModel.kt index f112dc9ce9b..a291778835a 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModel.kt @@ -18,6 +18,7 @@ import mega.privacy.android.domain.entity.node.FolderNode import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeChanges import mega.privacy.android.domain.usecase.GetParentNodeHandle +import mega.privacy.android.domain.usecase.IsNodeInRubbish import mega.privacy.android.domain.usecase.MonitorMediaDiscoveryView import nz.mega.sdk.MegaApiJava import java.util.Stack @@ -31,6 +32,7 @@ import javax.inject.Inject * @param monitorMediaDiscoveryView Monitor media discovery view settings * @param monitorNodeUpdates Monitor node updates * @param getFileBrowserParentNodeHandle To get parent handle of current node + * @param getIsNodeInRubbish To get current node is in rubbish */ @HiltViewModel class FileBrowserViewModel @Inject constructor( @@ -39,6 +41,7 @@ class FileBrowserViewModel @Inject constructor( private val monitorMediaDiscoveryView: MonitorMediaDiscoveryView, private val monitorNodeUpdates: MonitorNodeUpdates, private val getFileBrowserParentNodeHandle: GetParentNodeHandle, + private val getIsNodeInRubbish: IsNodeInRubbish, ) : ViewModel() { private val _state = MutableStateFlow(FileBrowserState()) @@ -52,6 +55,7 @@ class FileBrowserViewModel @Inject constructor( * Stack to maintain folder navigation clicks */ private val lastPositionStack = Stack() + private val handleStack = Stack() init { monitorMediaDiscovery() @@ -93,13 +97,18 @@ class FileBrowserViewModel @Inject constructor( * moved to rubbish bin * we are in same screen else will simply refresh nodes with parentID * @param changes [Map] of [Node], list of [NodeChanges] - `*/ - private fun checkForNodeIsInRubbish(changes: Map>) { + */ + private suspend fun checkForNodeIsInRubbish(changes: Map>) { changes.forEach { (node, _) -> if (node is FolderNode) { if (node.isInRubbishBin && _state.value.fileBrowserHandle == node.id.longValue) { - setBrowserParentHandle(node.parentId.longValue) - return@forEach + while (handleStack.isNotEmpty() && getIsNodeInRubbish(handleStack.peek())) { + handleStack.pop() + } + handleStack.takeIf { stack -> stack.isNotEmpty() }?.peek()?.let { parent -> + setBrowserParentHandle(parent) + return + } } } } @@ -112,6 +121,7 @@ class FileBrowserViewModel @Inject constructor( * @param handle the id of the current browser handle to set */ fun setBrowserParentHandle(handle: Long) = viewModelScope.launch { + handleStack.push(handle) _state.update { it.copy( fileBrowserHandle = handle, @@ -178,6 +188,7 @@ class FileBrowserViewModel @Inject constructor( fun onBackPressed() { _state.value.parentHandle?.let { setBrowserParentHandle(it) + handleStack.takeIf { stack -> stack.isNotEmpty() }?.pop() } } @@ -205,4 +216,4 @@ class FileBrowserViewModel @Inject constructor( pushPositionOnStack(lastFirstVisiblePosition) setBrowserParentHandle(handle) } -} +} \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModelTest.kt index f3dfeaf6f04..f0d434db6ae 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/clouddrive/FileBrowserViewModelTest.kt @@ -19,6 +19,7 @@ import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeChanges import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.usecase.GetParentNodeHandle +import mega.privacy.android.domain.usecase.IsNodeInRubbish import mega.privacy.android.domain.usecase.MonitorMediaDiscoveryView import nz.mega.sdk.MegaApiJava import nz.mega.sdk.MegaNode @@ -37,6 +38,7 @@ class FileBrowserViewModelTest { private lateinit var underTest: FileBrowserViewModel private val getRootFolder = mock() + private val isNodeInRubbish = mock() private val getBrowserChildrenNode = mock() private val monitorMediaDiscoveryView = mock { on { invoke() }.thenReturn( @@ -61,7 +63,8 @@ class FileBrowserViewModelTest { getBrowserChildrenNode = getBrowserChildrenNode, monitorMediaDiscoveryView = monitorMediaDiscoveryView, monitorNodeUpdates = monitorNodeUpdates, - getFileBrowserParentNodeHandle = getFileBrowserParentNodeHandle + getFileBrowserParentNodeHandle = getFileBrowserParentNodeHandle, + getIsNodeInRubbish = isNodeInRubbish ) } From ab8ca9cdac40d0d810389a70daf4d5e36d69f3b6 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 23 Dec 2022 12:14:39 +0530 Subject: [PATCH 040/334] Functions for pending actions, openShareDialog, upgradeSecurity are added in MegaApi & implementations added in MegaApiFacade --- .../android/data/facade/MegaApiFacade.kt | 16 +++++++++ .../data/gateway/api/MegaApiGateway.kt | 35 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index 30cf8505666..c42fd5885f7 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -32,6 +32,7 @@ import nz.mega.sdk.MegaSetElement import nz.mega.sdk.MegaSetElementList import nz.mega.sdk.MegaSetList import nz.mega.sdk.MegaShare +import nz.mega.sdk.MegaShareList import nz.mega.sdk.MegaTransfer import nz.mega.sdk.MegaTransferListenerInterface import nz.mega.sdk.MegaUser @@ -871,4 +872,19 @@ internal class MegaApiFacade @Inject constructor( ) = megaApi.getPublicNode(nodeFileLink, listener) override suspend fun cancelTransfers(direction: Int) = megaApi.cancelTransfers(direction) + + override suspend fun getUnverifiedIncomingShares(order: Int): MegaShareList = + megaApi.getUnverifiedIncomingShares(order) + + override suspend fun getUnverifiedOutgoingShares(order: Int): MegaShareList = + megaApi.getUnverifiedIncomingShares(order) + + override fun openShareDialog( + megaNode: MegaNode, + listener: MegaRequestListenerInterface, + ) = megaApi.openShareDialog(megaNode, listener) + + override fun upgradeSecurity(listener: MegaRequestListenerInterface) = + megaApi.upgradeSecurity(listener) + } diff --git a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt index 9937881b224..e89672f0626 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt @@ -15,6 +15,7 @@ import nz.mega.sdk.MegaSet import nz.mega.sdk.MegaSetElementList import nz.mega.sdk.MegaSetList import nz.mega.sdk.MegaShare +import nz.mega.sdk.MegaShareList import nz.mega.sdk.MegaTransfer import nz.mega.sdk.MegaTransferListenerInterface import nz.mega.sdk.MegaUser @@ -1675,4 +1676,38 @@ interface MegaApiGateway { * - MegaTransfer::TYPE_UPLOAD = 1 */ suspend fun cancelTransfers(direction: Int) + + /** + * Function to get unverified incoming shares from [MegaApi] + * + * @param order : Sort order + * @return List of [MegaShare] + */ + suspend fun getUnverifiedIncomingShares(order: Int): MegaShareList + + /** + * Function to get unverified outgoing shares from [MegaApi] + * + * @param order : Sort order + * @return List of [MegaShare] + */ + suspend fun getUnverifiedOutgoingShares(order: Int): MegaShareList + + /** + * Creates a new share key for the node if there is no share key already created. + * + * @param megaNode : [MegaNode] object which needs to be shared + * @param listener : Listener to track this request + */ + fun openShareDialog( + megaNode: MegaNode, + listener: MegaRequestListenerInterface, + ) + + /** + * Update cryptographic security + * + * @param listener : Listener to track this request + */ + fun upgradeSecurity(listener: MegaRequestListenerInterface) } From 8576a79947eb5f293b0c85a5c63e19f6e53b225f Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 26 Dec 2022 18:53:03 +0530 Subject: [PATCH 041/334] Return type changed to List --- .../java/mega/privacy/android/data/facade/MegaApiFacade.kt | 6 +++--- .../mega/privacy/android/data/gateway/api/MegaApiGateway.kt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index c42fd5885f7..75d157ff40d 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -14,6 +14,7 @@ import mega.privacy.android.data.listener.OptionalMegaTransferListenerInterface import mega.privacy.android.data.model.GlobalTransfer import mega.privacy.android.data.model.GlobalUpdate import mega.privacy.android.data.qualifier.MegaApi +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.qualifier.ApplicationScope import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaApiJava @@ -32,7 +33,6 @@ import nz.mega.sdk.MegaSetElement import nz.mega.sdk.MegaSetElementList import nz.mega.sdk.MegaSetList import nz.mega.sdk.MegaShare -import nz.mega.sdk.MegaShareList import nz.mega.sdk.MegaTransfer import nz.mega.sdk.MegaTransferListenerInterface import nz.mega.sdk.MegaUser @@ -873,10 +873,10 @@ internal class MegaApiFacade @Inject constructor( override suspend fun cancelTransfers(direction: Int) = megaApi.cancelTransfers(direction) - override suspend fun getUnverifiedIncomingShares(order: Int): MegaShareList = + override suspend fun getUnverifiedIncomingShares(order: Int): List = megaApi.getUnverifiedIncomingShares(order) - override suspend fun getUnverifiedOutgoingShares(order: Int): MegaShareList = + override suspend fun getUnverifiedOutgoingShares(order: Int): List = megaApi.getUnverifiedIncomingShares(order) override fun openShareDialog( diff --git a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt index e89672f0626..0acef30291b 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt @@ -3,6 +3,7 @@ package mega.privacy.android.data.gateway.api import kotlinx.coroutines.flow.Flow import mega.privacy.android.data.model.GlobalTransfer import mega.privacy.android.data.model.GlobalUpdate +import mega.privacy.android.domain.entity.ShareData import nz.mega.sdk.MegaCancelToken import nz.mega.sdk.MegaContactRequest import nz.mega.sdk.MegaError @@ -15,7 +16,6 @@ import nz.mega.sdk.MegaSet import nz.mega.sdk.MegaSetElementList import nz.mega.sdk.MegaSetList import nz.mega.sdk.MegaShare -import nz.mega.sdk.MegaShareList import nz.mega.sdk.MegaTransfer import nz.mega.sdk.MegaTransferListenerInterface import nz.mega.sdk.MegaUser @@ -1683,7 +1683,7 @@ interface MegaApiGateway { * @param order : Sort order * @return List of [MegaShare] */ - suspend fun getUnverifiedIncomingShares(order: Int): MegaShareList + suspend fun getUnverifiedIncomingShares(order: Int): List /** * Function to get unverified outgoing shares from [MegaApi] @@ -1691,7 +1691,7 @@ interface MegaApiGateway { * @param order : Sort order * @return List of [MegaShare] */ - suspend fun getUnverifiedOutgoingShares(order: Int): MegaShareList + suspend fun getUnverifiedOutgoingShares(order: Int): List /** * Creates a new share key for the node if there is no share key already created. From ec59d1b6e8b385a8ce1bf89b2adfae20c1109a3d Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 27 Dec 2022 14:54:15 +0530 Subject: [PATCH 042/334] Security upgrade dialog strings added --- app/src/main/res/values/strings.xml | 110 +++++++--------------------- 1 file changed, 27 insertions(+), 83 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 59c7ee139a2..87336726d87 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -790,7 +790,7 @@ Save - Recovery key copied to clipboard. Save it to a safe place where you can easily access later. + The Recovery key has been successfully copied Change @@ -1506,6 +1506,8 @@ You have received %1$s storage space for verifying your phone number. You have received %1$s storage space as your free registration bonus. + + Bonus expires in %1$d days Share folder @@ -3581,6 +3583,22 @@ Payment methods Proceed + + Pro Lite monthly + + Pro Lite yearly + + Pro I monthly + + Pro I yearly + + Pro II monthly + + Pro II yearly + + Pro III monthly + + Pro III yearly Remove as host @@ -3915,6 +3933,8 @@ Start chatting now Chat securely and privately, with anyone and on any device, knowing that no one can read your chats, not even MEGA. + + Archive meeting Start meeting now @@ -4007,6 +4027,8 @@ You’ve already added all your contacts to this chat. If you want to add more participants, first invite them to your contact list. Saved image to your device gallery + + [A]Renewal date:[/A] [B]%s[/B] Enter album name @@ -4061,7 +4083,7 @@ Recurring meeting - %s Weekly + %s weekly Media discovery view @@ -4116,85 +4138,7 @@ Bonus expires in %1$d day Bonus expires in %1$d days - - Description - - Remove from album? - - To enable camera uploads, grant MEGA access to your photos and other media on your device. - - Grant access - - Don’t grant - - - Removed %d item from “%s” - Removed %d items from “%s” - - - One-off meeting - - Resume video? - - %1$s will resume from %2$s - - Resume - - Restart - - [A]%s [/A][B]invited you to a meeting scheduled for:[/B] - - [A]%s cancelled[/A][B] the meeting scheduled for:[/B] - - [A]%s updated the meeting name[/A][B] from “%s” to [/B]“[A]%s[/A]“ - - [A]%s updated[/A][B] the meeting date[/B] - - [A]%s updated[/A][B] the meeting time[/B] - - [A]%s updated[/A][B] the meeting description[/B] - - [A]%s updated[/A][B] the meeting details scheduled for:[/B] - - Access denied - - You denied MEGA access to your device’s storage and media files. If you’d like to continue sharing, allow MEGA permission. - - Allow permission - - Don’t allow - - Occurrences - - Occurs daily - - Occurs weekly - - Occurs monthly - - See more occurrences - - %s Monthly - - %s Daily - - [A]%s [/A][B]invited you to a recurring meeting scheduled for:[/B] - - [A]%s updated[/A][B] the recurring meeting description[/B] - - [A]%s updated[/A][B] the recurring meeting details scheduled for:[/B] - - [A]%s updated[/A][B] an occurrence to:[/B] - - [A]%s updated[/A][B] the recurring meeting time[/B] - - [A]%s updated[/A][B] the recurring meeting frequency[/B] - - [A]%s cancelled[/A][B] the meeting and all its occurrences[/B] - - [A]%s cancelled[/A][B] the occurrence scheduled for:[/B] - - This link hasn’t been generated with the account you’re currently logged into. Log in to the account related to this link to verify your email address. - - Unable to update email address + Security upgrade + Your account’s security is now being upgraded. This will happen only once. If you have seen this message for this account before, press Cancel. + You are currently sharing the following folders: %s \ No newline at end of file From 16889d9ce29c967fefda693c2c2b0c645df8ea5e Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 08:50:09 +0530 Subject: [PATCH 043/334] Function return types & parameters updated. openShareDialog & upgradeSecurity changed to suspend --- .../mega/privacy/android/data/facade/MegaApiFacade.kt | 8 ++++---- .../privacy/android/data/gateway/api/MegaApiGateway.kt | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index 75d157ff40d..203c7772cb8 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -873,18 +873,18 @@ internal class MegaApiFacade @Inject constructor( override suspend fun cancelTransfers(direction: Int) = megaApi.cancelTransfers(direction) - override suspend fun getUnverifiedIncomingShares(order: Int): List = + override suspend fun getUnverifiedIncomingShares(order: Int): List = megaApi.getUnverifiedIncomingShares(order) - override suspend fun getUnverifiedOutgoingShares(order: Int): List = + override suspend fun getUnverifiedOutgoingShares(order: Int): List = megaApi.getUnverifiedIncomingShares(order) - override fun openShareDialog( + override suspend fun openShareDialog( megaNode: MegaNode, listener: MegaRequestListenerInterface, ) = megaApi.openShareDialog(megaNode, listener) - override fun upgradeSecurity(listener: MegaRequestListenerInterface) = + override suspend fun upgradeSecurity(listener: MegaRequestListenerInterface) = megaApi.upgradeSecurity(listener) } diff --git a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt index 0acef30291b..0e991d7fb54 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt @@ -1683,7 +1683,7 @@ interface MegaApiGateway { * @param order : Sort order * @return List of [MegaShare] */ - suspend fun getUnverifiedIncomingShares(order: Int): List + suspend fun getUnverifiedIncomingShares(order: Int): List /** * Function to get unverified outgoing shares from [MegaApi] @@ -1691,7 +1691,7 @@ interface MegaApiGateway { * @param order : Sort order * @return List of [MegaShare] */ - suspend fun getUnverifiedOutgoingShares(order: Int): List + suspend fun getUnverifiedOutgoingShares(order: Int): List /** * Creates a new share key for the node if there is no share key already created. @@ -1699,7 +1699,7 @@ interface MegaApiGateway { * @param megaNode : [MegaNode] object which needs to be shared * @param listener : Listener to track this request */ - fun openShareDialog( + suspend fun openShareDialog( megaNode: MegaNode, listener: MegaRequestListenerInterface, ) @@ -1709,5 +1709,5 @@ interface MegaApiGateway { * * @param listener : Listener to track this request */ - fun upgradeSecurity(listener: MegaRequestListenerInterface) + suspend fun upgradeSecurity(listener: MegaRequestListenerInterface) } From 08dee96745154343071e90527d861fc99e650771 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 09:02:35 +0530 Subject: [PATCH 044/334] FillMaxWidth & FillMaxHeight replaced with FillMaxSize --- .../SecurityUpgradeDialogView.kt | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt new file mode 100644 index 00000000000..06956ba3a26 --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt @@ -0,0 +1,127 @@ +package mega.privacy.android.app.presentation.fingerprintauth + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import mega.privacy.android.app.R +import mega.privacy.android.presentation.theme.body2 +import mega.privacy.android.presentation.theme.jade_300 +import mega.privacy.android.presentation.theme.subtitle1 + +/** + * Security upgrade dialog body + * + * @param folderName : Name of the folder for which security settings will upgrade + * @param onOkClick : Ok button click listener + * @param onCancelClick : Cancel button click listener + */ +@Composable +fun SecurityUpgradeDialogView( + folderName: String, + onOkClick: () -> Unit, + onCancelClick: () -> Unit, +) { + Surface(modifier = Modifier + .padding(10.dp) + .fillMaxSize(), + shape = RoundedCornerShape(8.dp), + color = if (MaterialTheme.colors.isLight) { + Color.White + } else { + Color.Transparent + }, + content = { + Column(modifier = Modifier.padding(start = 16.dp, end = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + content = { + Image(modifier = Modifier + .height(140.dp) + .width(114.dp) + .testTag("HeaderImage"), + imageVector = ImageVector.vectorResource(id = R.drawable.ic_contact_verification_required), + contentDescription = "Empty") + + Text(text = stringResource(id = R.string.shared_items_security_upgrade_dialog_title), + style = subtitle1, + color = if (MaterialTheme.colors.isLight) { + Color.Black + } else { + Color.White + }) + + Spacer(Modifier.height(16.dp)) + + Text(text = stringResource(id = R.string.shared_items_security_upgrade_dialog_content), + style = body2.copy(textAlign = TextAlign.Center), + color = if (MaterialTheme.colors.isLight) { + Color.Black + } else { + Color.White + }) + + Spacer(Modifier.height(20.dp)) + + Text(modifier = Modifier.testTag("SharedNodeInfo"), + text = stringResource(id = R.string.shared_items_security_upgrade_dialog_node_sharing_info, + folderName), + style = body2.copy(textAlign = TextAlign.Center), + color = if (MaterialTheme.colors.isLight) { + Color.Black + } else { + Color.White + }) + + Spacer(Modifier.height(20.dp)) + + Button(modifier = Modifier + .height(45.dp) + .fillMaxWidth() + .padding(start = 25.dp, end = 25.dp), + shape = RoundedCornerShape(8.dp), + content = { + Text(text = stringResource(id = R.string.general_ok), + color = Color.White) + }, + colors = ButtonDefaults.buttonColors(backgroundColor = jade_300), + onClick = onOkClick) + + Spacer(Modifier.height(10.dp)) + + Button(modifier = Modifier + .height(45.dp) + .fillMaxWidth() + .padding(start = 25.dp, end = 25.dp), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors(backgroundColor = if (MaterialTheme.colors.isLight) Color.White else Color.DarkGray), + onClick = onCancelClick) { + Text(text = stringResource(id = R.string.button_cancel), + color = if (MaterialTheme.colors.isLight) { + Color.Black + } else { + jade_300 + }) + } + }) + }) +} \ No newline at end of file From 2153d0ccaac8b03179a236a9bd1dc40c9da1b10b Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 09:50:08 +0530 Subject: [PATCH 045/334] Suspend removed & imports optimised --- .../java/mega/privacy/android/data/facade/MegaApiFacade.kt | 5 ++--- .../mega/privacy/android/data/gateway/api/MegaApiGateway.kt | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index 203c7772cb8..d25712811dd 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -14,7 +14,6 @@ import mega.privacy.android.data.listener.OptionalMegaTransferListenerInterface import mega.privacy.android.data.model.GlobalTransfer import mega.privacy.android.data.model.GlobalUpdate import mega.privacy.android.data.qualifier.MegaApi -import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.qualifier.ApplicationScope import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaApiJava @@ -879,12 +878,12 @@ internal class MegaApiFacade @Inject constructor( override suspend fun getUnverifiedOutgoingShares(order: Int): List = megaApi.getUnverifiedIncomingShares(order) - override suspend fun openShareDialog( + override fun openShareDialog( megaNode: MegaNode, listener: MegaRequestListenerInterface, ) = megaApi.openShareDialog(megaNode, listener) - override suspend fun upgradeSecurity(listener: MegaRequestListenerInterface) = + override fun upgradeSecurity(listener: MegaRequestListenerInterface) = megaApi.upgradeSecurity(listener) } diff --git a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt index 0e991d7fb54..bfc8144b431 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt @@ -3,7 +3,6 @@ package mega.privacy.android.data.gateway.api import kotlinx.coroutines.flow.Flow import mega.privacy.android.data.model.GlobalTransfer import mega.privacy.android.data.model.GlobalUpdate -import mega.privacy.android.domain.entity.ShareData import nz.mega.sdk.MegaCancelToken import nz.mega.sdk.MegaContactRequest import nz.mega.sdk.MegaError @@ -1699,7 +1698,7 @@ interface MegaApiGateway { * @param megaNode : [MegaNode] object which needs to be shared * @param listener : Listener to track this request */ - suspend fun openShareDialog( + fun openShareDialog( megaNode: MegaNode, listener: MegaRequestListenerInterface, ) @@ -1709,5 +1708,5 @@ interface MegaApiGateway { * * @param listener : Listener to track this request */ - suspend fun upgradeSecurity(listener: MegaRequestListenerInterface) + fun upgradeSecurity(listener: MegaRequestListenerInterface) } From 1f7c5fb7c6156c8c0a2a95845a533a123eb4954a Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 11:03:20 +0530 Subject: [PATCH 046/334] openShareDialog , upgradeSecurity changed to suspend functions. --- .../data/repository/DefaultFilesRepository.kt | 0 .../data/repository/MegaNodeRepository.kt | 21 +++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 data/src/main/java/mega/privacy/android/data/repository/DefaultFilesRepository.kt diff --git a/data/src/main/java/mega/privacy/android/data/repository/DefaultFilesRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/DefaultFilesRepository.kt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt index 45d2b31e06b..93e10042a6d 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt @@ -1,6 +1,7 @@ package mega.privacy.android.data.repository import mega.privacy.android.domain.entity.FolderVersionInfo +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.exception.MegaException @@ -236,14 +237,26 @@ interface MegaNodeRepository { /** * Provides Unverified incoming shares count from SDK * - * @return Integer count + * @return List of [ShareData] */ - suspend fun getUnVerifiedInComingShares(): Int + suspend fun getUnVerifiedInComingShares(order: SortOrder): List /** * Provides Unverified outgoing shares count from SDK * - * @return Integer count + * @return List of [ShareData] */ - suspend fun getUnverifiedOutgoingShares(): Int + suspend fun getUnverifiedOutgoingShares(order: SortOrder): List + + /** + * Creates a new share key for the node if there is no share key already created. + * + * @param megaNode : [MegaNode] object which needs to be shared + */ + suspend fun openShareDialog(megaNode: MegaNode) + + /** + * Update cryptographic security + */ + suspend fun upgradeSecurity() } \ No newline at end of file From f2ff11d3aea0b991bea12e0ad3b63cd64b786843 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 11:15:39 +0530 Subject: [PATCH 047/334] Return type & input changed --- .../android/domain/usecase/GetUnverifiedIncomingShares.kt | 5 ++++- .../android/domain/usecase/GetUnverifiedOutgoingShares.kt | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedIncomingShares.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedIncomingShares.kt index acb7fb775f6..da43e7da4fb 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedIncomingShares.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedIncomingShares.kt @@ -1,5 +1,8 @@ package mega.privacy.android.domain.usecase +import mega.privacy.android.domain.entity.ShareData +import mega.privacy.android.domain.entity.SortOrder + /** * GetUnverifiedIncomingShares Use case */ @@ -8,5 +11,5 @@ fun interface GetUnverifiedIncomingShares { /** * @return Flow of unverified incoming shares */ - suspend operator fun invoke(): Int + suspend operator fun invoke(order: SortOrder): List } \ No newline at end of file diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedOutgoingShares.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedOutgoingShares.kt index db0354ecf47..2482b46ca62 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedOutgoingShares.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedOutgoingShares.kt @@ -1,5 +1,8 @@ package mega.privacy.android.domain.usecase +import mega.privacy.android.domain.entity.ShareData +import mega.privacy.android.domain.entity.SortOrder + /** * GetUnverifiedOutgoingShares Use case */ @@ -8,5 +11,5 @@ fun interface GetUnverifiedOutgoingShares { /** * @return Flow of unverified outgoing shares */ - suspend operator fun invoke(): Int + suspend operator fun invoke(order: SortOrder): List } \ No newline at end of file From 97db7bf490a7c1e8a9b64a67359a1c5207ad1039 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 11:37:59 +0530 Subject: [PATCH 048/334] Viewmodel modified to solve compile issue --- .../presentation/shares/incoming/IncomingSharesViewModel.kt | 2 +- .../presentation/shares/incoming/model/IncomingSharesState.kt | 3 ++- .../presentation/shares/outgoing/OutgoingSharesViewModel.kt | 2 +- .../presentation/shares/outgoing/model/OutgoingSharesState.kt | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index e57f1cefb4a..50b899df385 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -74,7 +74,7 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() _state.update { - it.copy(unVerifiedInComingShares = getUnverifiedInComingShares()) + it.copy(unVerifiedInComingShares = getUnverifiedInComingShares(_state.value.sortOrder)) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index b59cb756f5b..38caa5d6334 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -1,5 +1,6 @@ package mega.privacy.android.app.presentation.shares.incoming.model +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import nz.mega.sdk.MegaNode @@ -24,7 +25,7 @@ data class IncomingSharesState( val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val unVerifiedInComingShares: Int = 0, + val unVerifiedInComingShares: List = emptyList(), val isMandatoryFingerprintVerificationNeeded: Boolean = false, ) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index c07bf003304..a301d30e2fe 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -59,7 +59,7 @@ class OutgoingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() _state.update { - it.copy(unVerifiedOutGoingShares = getUnverifiedOutgoingShares()) + it.copy(unVerifiedOutGoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder)) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index d09e2fb7849..69f31bfebd8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -1,5 +1,6 @@ package mega.privacy.android.app.presentation.shares.outgoing.model +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import nz.mega.sdk.MegaNode @@ -24,7 +25,7 @@ data class OutgoingSharesState( val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val unVerifiedOutGoingShares: Int = 0, + val unVerifiedOutGoingShares: List = emptyList(), val isMandatoryFingerprintVerificationNeeded: Boolean = false, ) { From 20ed5c85475965b27588f791704f4d562488e0b5 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 12:33:39 +0530 Subject: [PATCH 049/334] Failing unit test modified to get success --- .../mega/privacy/android/app/di/TestGetNodeModule.kt | 4 ++-- .../shares/incoming/IncomingSharesViewModelTest.kt | 9 ++++++--- .../shares/outgoing/OutgoingSharesViewModelTest.kt | 6 ++++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt index 4d6a6d6281f..26fe7ed5176 100644 --- a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt +++ b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt @@ -40,11 +40,11 @@ object TestGetNodeModule { @Provides fun provideGetUnVerifiedInComingShares() = mock() { - onBlocking { invoke() }.thenReturn(3) + onBlocking { invoke(any()) }.thenReturn(emptyList()) } @Provides fun provideGetUnverifiedOutGoingShares() = mock() { - onBlocking { invoke() }.thenReturn(3) + onBlocking { invoke(any()) }.thenReturn(emptyList()) } } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index dbc8cb26374..2b7a7a4b989 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -15,6 +15,7 @@ import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeChanges @@ -94,7 +95,7 @@ class IncomingSharesViewModelTest { assertThat(initial.isInvalidHandle).isEqualTo(true) assertThat(initial.incomingParentHandle).isEqualTo(null) assertThat(initial.sortOrder).isEqualTo(SortOrder.ORDER_NONE) - assertThat(initial.unVerifiedInComingShares).isEqualTo(0) + assertThat(initial.unVerifiedInComingShares).isEmpty() } } @@ -519,10 +520,12 @@ class IncomingSharesViewModelTest { @Test fun `test that unverified incoming shares are returned`() = runTest { - whenever(getUnverifiedInComingShares()).thenReturn(3) + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + whenever(getUnverifiedInComingShares(underTest.state.value.sortOrder)).thenReturn(listOf( + shareData)) initViewModel() underTest.state.test { - assertThat(awaitItem().unVerifiedInComingShares).isEqualTo(3) + assertThat(awaitItem().unVerifiedInComingShares).isNotEmpty() } } } \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index 26f9c3c2848..9acc4133d7c 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -14,6 +14,7 @@ import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeId @@ -60,7 +61,8 @@ class OutgoingSharesViewModelTest { } private val getUnverifiedOutgoingShares = mock() { - onBlocking { invoke() }.thenReturn(5) + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } @Before @@ -446,7 +448,7 @@ class OutgoingSharesViewModelTest { fun `test that unverified incoming shares are returned`() = runTest { initViewModel() underTest.state.test { - assertThat(awaitItem().unVerifiedOutGoingShares).isEqualTo(5) + assertThat(awaitItem().unVerifiedOutGoingShares).isNotEmpty() } } } From 6223ab77af8ccb6da829e4f68a72668c255f1b63 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 15:26:04 +0530 Subject: [PATCH 050/334] Pending actions count badge added on Bottom Sheet bar --- .../android/app/main/ManagerActivity.java | 7 +- .../presentation/manager/ManagerViewModel.kt | 33 +++++++++ .../manager/model/ManagerState.kt | 2 + .../manager/ManagerViewModelTest.kt | 70 +++++++++++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index a63e04d5c70..9ed0603efc8 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -10270,8 +10270,11 @@ public void setChatBadge() { public void setPendingActionsBadge() { if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded()) { sharedItemsView.addView(pendingActionsBadge); - TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.chat_badge_text); - tvPendingActionsCount.setText("5"); + ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { + TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.chat_badge_text); + tvPendingActionsCount.setText(managerState.getPendingActionsCount()); + return Unit.INSTANCE; + }); } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index b7412bc114a..6326ca2494b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import mega.privacy.android.app.domain.usecase.GetInboxNode import mega.privacy.android.app.domain.usecase.GetPrimarySyncHandle +import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates @@ -32,6 +33,7 @@ import mega.privacy.android.app.presentation.manager.model.TransfersTab import mega.privacy.android.app.utils.livedata.SingleLiveEvent import mega.privacy.android.data.model.GlobalUpdate import mega.privacy.android.domain.entity.Product +import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.StorageState import mega.privacy.android.domain.entity.billing.MegaPurchase import mega.privacy.android.domain.entity.contacts.ContactRequest @@ -45,6 +47,8 @@ import mega.privacy.android.domain.usecase.GetExtendedAccountDetail import mega.privacy.android.domain.usecase.GetFullAccountInfo import mega.privacy.android.domain.usecase.GetNumUnreadUserAlerts import mega.privacy.android.domain.usecase.GetPricing +import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares +import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares import mega.privacy.android.domain.usecase.HasInboxChildren import mega.privacy.android.domain.usecase.MonitorConnectivity import mega.privacy.android.domain.usecase.MonitorContactRequestUpdates @@ -65,6 +69,7 @@ import javax.inject.Inject * * @param monitorNodeUpdates Monitor global node updates * @param monitorGlobalUpdates Monitor global updates + * @param getRubbishBinChildrenNode Fetch the rubbish bin nodes * @param monitorContactRequestUpdates * @param getInboxNode * @param getNumUnreadUserAlerts @@ -81,6 +86,7 @@ import javax.inject.Inject class ManagerViewModel @Inject constructor( monitorNodeUpdates: MonitorNodeUpdates, private val monitorGlobalUpdates: MonitorGlobalUpdates, + private val getRubbishBinChildrenNode: GetRubbishBinChildrenNode, monitorContactRequestUpdates: MonitorContactRequestUpdates, private val getInboxNode: GetInboxNode, private val getNumUnreadUserAlerts: GetNumUnreadUserAlerts, @@ -101,6 +107,8 @@ class ManagerViewModel @Inject constructor( private val getPricing: GetPricing, private val getFullAccountInfo: GetFullAccountInfo, private val getActiveSubscription: GetActiveSubscription, + private val getUnverifiedInComingShares: GetUnverifiedIncomingShares, + private val getUnverifiedOutgoingShares: GetUnverifiedOutgoingShares, ) : ViewModel() { /** @@ -139,6 +147,7 @@ class ManagerViewModel @Inject constructor( ) init { + viewModelScope.launch { monitorNodeUpdates().collect { val nodeList = it.changes.keys.toList() @@ -154,6 +163,14 @@ class ManagerViewModel @Inject constructor( _state.update(it) } } + + viewModelScope.launch { + val incomingShares = getUnverifiedInComingShares(SortOrder.ORDER_DEFAULT_ASC).size + val outgoingShares = getUnverifiedOutgoingShares(SortOrder.ORDER_DEFAULT_ASC).size + _state.update { + it.copy(pendingActionsCount = incomingShares + outgoingShares) + } + } } /** @@ -163,6 +180,12 @@ class ManagerViewModel @Inject constructor( private val _updates = monitorGlobalUpdates() .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) + /** + * Monitor global node updates + */ + private val _updateNodes = monitorNodeUpdates() + .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) + /** * Monitor contact requests */ @@ -208,6 +231,16 @@ class ManagerViewModel @Inject constructor( .map { Event(it) } .asLiveData() + /** + * Update Rubbish Nodes when a node update callback happens + */ + val updateRubbishBinNodes: LiveData>> = + _updateNodes + .also { Timber.d("onRubbishNodesUpdate") } + .mapNotNull { getRubbishBinChildrenNode(_state.value.rubbishBinParentHandle) } + .map { Event(it) } + .asLiveData() + /** * On my avatar file changed */ diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index 7b16ffcd029..19126a222a8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -11,6 +11,7 @@ package mega.privacy.android.app.presentation.manager.model * @param shouldStopCameraUpload camera upload should be stopped or not * @param shouldSendCameraBroadcastEvent broadcast event should be sent or not * @param nodeUpdateReceived one-off event to notify UI that a node update occurred + * @param pendingActionsCount Pending actions count */ data class ManagerState( val isFirstNavigationLevel: Boolean = true, @@ -21,4 +22,5 @@ data class ManagerState( val shouldStopCameraUpload: Boolean = false, val shouldSendCameraBroadcastEvent: Boolean = false, val nodeUpdateReceived: Boolean = false, + val pendingActionsCount: Int = 0 ) diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index 7933d257289..f31c5bcc527 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -18,12 +18,14 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetInboxNode import mega.privacy.android.app.domain.usecase.GetPrimarySyncHandle +import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates import mega.privacy.android.app.presentation.manager.ManagerViewModel import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.manager.model.TransfersTab import mega.privacy.android.data.model.GlobalUpdate +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.contacts.ContactRequest import mega.privacy.android.domain.entity.contacts.ContactRequestStatus @@ -31,6 +33,8 @@ import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.usecase.CheckCameraUpload import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetNumUnreadUserAlerts +import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares +import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares import mega.privacy.android.domain.usecase.HasInboxChildren import mega.privacy.android.domain.usecase.MonitorConnectivity import mega.privacy.android.domain.usecase.MonitorContactRequestUpdates @@ -41,6 +45,7 @@ import mega.privacy.android.domain.usecase.viewtype.MonitorViewType import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import test.mega.privacy.android.app.presentation.shares.FakeMonitorUpdates @@ -52,6 +57,7 @@ class ManagerViewModelTest { private val monitorGlobalUpdates = mock() private val monitorNodeUpdates = FakeMonitorUpdates() + private val getRubbishBinNodeByHandle = mock() private val getNumUnreadUserAlerts = mock() private val hasInboxChildren = mock() private val monitorContactRequestUpdates = mock() @@ -66,6 +72,14 @@ class ManagerViewModelTest { private val checkCameraUpload = mock() private val getCloudSortOrder = mock() private val monitorConnectivity = mock() + private val getUnverifiedOutgoingShares = mock { + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) + } + private val getUnverifiedInComingShares = mock { + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) + } @get:Rule var instantExecutorRule = InstantTaskExecutorRule() @@ -83,6 +97,7 @@ class ManagerViewModelTest { underTest = ManagerViewModel( monitorNodeUpdates = monitorNodeUpdates, monitorGlobalUpdates = monitorGlobalUpdates, + getRubbishBinChildrenNode = getRubbishBinNodeByHandle, monitorContactRequestUpdates = monitorContactRequestUpdates, getNumUnreadUserAlerts = getNumUnreadUserAlerts, hasInboxChildren = hasInboxChildren, @@ -103,6 +118,8 @@ class ManagerViewModelTest { getPricing = mock(), getFullAccountInfo = mock(), getActiveSubscription = mock(), + getUnverifiedInComingShares = getUnverifiedInComingShares, + getUnverifiedOutgoingShares = getUnverifiedOutgoingShares, ) } @@ -128,6 +145,7 @@ class ManagerViewModelTest { setUnderTest() underTest.state.test { val initial = awaitItem() + assertThat(initial.rubbishBinParentHandle).isEqualTo(-1L) assertThat(initial.isFirstNavigationLevel).isTrue() assertThat(initial.sharesTab).isEqualTo(SharesTab.INCOMING_TAB) assertThat(initial.transfersTab).isEqualTo(TransfersTab.NONE) @@ -138,6 +156,19 @@ class ManagerViewModelTest { } } + @Test + fun `test that rubbish bin parent handle is updated if new value provided`() = runTest { + setUnderTest() + + underTest.state.map { it.rubbishBinParentHandle }.distinctUntilChanged() + .test { + val newValue = 123456789L + assertThat(awaitItem()).isEqualTo(-1L) + underTest.setRubbishBinParentHandle(newValue) + assertThat(awaitItem()).isEqualTo(newValue) + } + } + @Test fun `test that is first navigation level is updated if new value provided`() = runTest { setUnderTest() @@ -201,6 +232,37 @@ class ManagerViewModelTest { underTest.updateContactsRequests.test().assertNoValue() } + @Test + fun `test that rubbish bin node updates live data is set when node updates triggered from use case`() = + runTest { + whenever(getRubbishBinNodeByHandle(any())).thenReturn(listOf(mock(), mock())) + + setUnderTest() + + runCatching { + val result = + underTest.updateRubbishBinNodes.test().awaitValue(50, TimeUnit.MILLISECONDS) + monitorNodeUpdates.emit(listOf(mock())) + result + }.onSuccess { result -> + result.assertValue { it.getContentIfNotHandled()?.size == 2 } + } + } + + @Test + fun `test that rubbish bin node updates live data is not set when get rubbish bin node returns a null list`() = + runTest { + whenever(getRubbishBinNodeByHandle(any())).thenReturn(null) + + setUnderTest() + + runCatching { + underTest.updateRubbishBinNodes.test().awaitValue(50, TimeUnit.MILLISECONDS) + }.onSuccess { result -> + result.assertNoValue() + } + } + @Test fun `test that user updates live data is set when user updates triggered from use case`() = runTest { @@ -345,4 +407,12 @@ class ManagerViewModelTest { assertThat(awaitItem()).isFalse() } } + + @Test + fun `test that pending actions count is not null`() = runTest { + setUnderTest() + underTest.state.map { it.pendingActionsCount }.distinctUntilChanged().test { + assertThat(awaitItem()).isEqualTo(2) + } + } } From a3af814a83450053d18c2f7f5895d1d9ebb1b2cf Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 22:04:42 +0530 Subject: [PATCH 051/334] AND-15314 NodeOptionsBottomSheetDialogFragment UI updated if node is unverified --- .../NodeOptionsBottomSheetDialogFragment.java | 107 +++++++++++------- .../res/layout/bottom_sheet_node_item.xml | 4 +- 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 11ba6031ce7..3d12fe86310 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -89,9 +89,11 @@ import mega.privacy.android.app.main.ManagerActivity; import mega.privacy.android.app.main.VersionsFileActivity; import mega.privacy.android.app.main.controllers.NodeController; +import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity; import mega.privacy.android.app.presentation.manager.model.SharesTab; import mega.privacy.android.app.presentation.search.SearchViewModel; import mega.privacy.android.app.utils.AlertDialogUtil; +import mega.privacy.android.app.utils.Constants; import mega.privacy.android.app.utils.MegaNodeUtil; import mega.privacy.android.app.utils.StringResourcesUtils; import mega.privacy.android.app.utils.ViewUtils; @@ -254,18 +256,6 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat TextView optionRubbishBin = contentView.findViewById(R.id.rubbish_bin_option); TextView optionRemove = contentView.findViewById(R.id.remove_option); - if(searchViewModel.getMandatoryFingerPrintVerificationState().getValue()) { - ////TODO This flag for false for now. This will get manipulated after SDK changes - TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); - nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); - optionVerifyUser.setVisibility(View.VISIBLE); - optionVerifyUser.setOnClickListener(this); - optionDownload.setOnClickListener(null); - optionOffline.setOnClickListener(null); - optionDownload.setVisibility(View.GONE); - optionOffline.setVisibility(View.GONE); - } - optionEdit.setOnClickListener(this); optionLabel.setOnClickListener(this); optionFavourite.setOnClickListener(this); @@ -304,6 +294,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat LinearLayout separatorDownload = contentView.findViewById(R.id.separator_download_options); LinearLayout separatorShares = contentView.findViewById(R.id.separator_share_options); LinearLayout separatorModify = contentView.findViewById(R.id.separator_modify_options); + LinearLayout separatorLabel = contentView.findViewById(R.id.label_separator); if (!isScreenInPortrait(requireContext())) { Timber.d("Landscape configuration"); @@ -333,45 +324,78 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } if (isOnline(requireContext())) { - nodeName.setText(node.getName()); - if (node.isFolder()) { - optionVersionsLayout.setVisibility(View.GONE); - nodeInfo.setText(getMegaNodeFolderInfo(node)); - nodeVersionsIcon.setVisibility(View.GONE); - - nodeThumb.setImageResource(getFolderIcon(node, drawerItem)); + if(!searchViewModel.getMandatoryFingerPrintVerificationState().getValue()) { + ////TODO This flag for false for now. This will get manipulated after SDK changes + showOwnerSharedFolder(); + TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); + optionVerifyUser.setText(StringResourcesUtils.getString(R.string.shared_items_bottom_sheet_menu_verify_user, getMegaUserNameDB(user))); + nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + optionVerifyUser.setVisibility(View.VISIBLE); + optionVerifyUser.setOnClickListener(this); + + //Removing the click listener & making it View.GONE + optionDownload.setOnClickListener(null); + optionDownload.setVisibility(View.GONE); + + //Removing the click listener & making it View.GONE + optionOffline.setOnClickListener(null); + optionOffline.setVisibility(View.GONE); - if (isEmptyFolder(node)) { - counterSave--; - optionOffline.setVisibility(View.GONE); - } + separatorDownload.setVisibility(View.GONE); + separatorLabel.setVisibility(View.GONE); + separatorOpen.setVisibility(View.GONE); + separatorModify.setVisibility(View.GONE); + separatorShares.setVisibility(View.GONE); - counterShares--; + //Removing the click listener & making it View.GONE + optionSendChat.setOnClickListener(null); optionSendChat.setVisibility(View.GONE); - } else { - if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize()) - && accessLevel >= MegaShare.ACCESS_READWRITE) { - optionEdit.setVisibility(View.VISIBLE); - } - nodeInfo.setText(getFileInfo(node)); + //Removing the click listener & making it View.GONE + optionCopy.setOnClickListener(null); + optionCopy.setVisibility(View.GONE); - if (megaApi.hasVersions(node)) { - nodeVersionsIcon.setVisibility(View.VISIBLE); - optionVersionsLayout.setVisibility(View.VISIBLE); - versions.setText(String.valueOf(megaApi.getNumVersions(node))); - } else { - nodeVersionsIcon.setVisibility(View.GONE); + } else { + nodeName.setText(node.getName()); + if (node.isFolder()) { optionVersionsLayout.setVisibility(View.GONE); - } + nodeInfo.setText(getMegaNodeFolderInfo(node)); + nodeVersionsIcon.setVisibility(View.GONE); - setNodeThumbnail(requireContext(), node, nodeThumb); + nodeThumb.setImageResource(getFolderIcon(node, drawerItem)); + + if (isEmptyFolder(node)) { + counterSave--; + optionOffline.setVisibility(View.GONE); + } - if (isTakenDown) { counterShares--; optionSendChat.setVisibility(View.GONE); } else { - optionSendChat.setVisibility(View.VISIBLE); + if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize()) + && accessLevel >= MegaShare.ACCESS_READWRITE) { + optionEdit.setVisibility(View.VISIBLE); + } + + nodeInfo.setText(getFileInfo(node)); + + if (megaApi.hasVersions(node)) { + nodeVersionsIcon.setVisibility(View.VISIBLE); + optionVersionsLayout.setVisibility(View.VISIBLE); + versions.setText(String.valueOf(megaApi.getNumVersions(node))); + } else { + nodeVersionsIcon.setVisibility(View.GONE); + optionVersionsLayout.setVisibility(View.GONE); + } + + setNodeThumbnail(requireContext(), node, nodeThumb); + + if (isTakenDown) { + counterShares--; + optionSendChat.setVisibility(View.GONE); + } else { + optionSendChat.setVisibility(View.VISIBLE); + } } } } @@ -1028,6 +1052,9 @@ public void onClick(View v) { requireActivity().startActivityForResult(version, REQUEST_CODE_DELETE_VERSIONS_HISTORY); break; case R.id.verify_user_option: + Intent authenticityCredentialsIntent = new Intent(getActivity(), AuthenticityCredentialsActivity.class); + authenticityCredentialsIntent.putExtra(Constants.EMAIL, user.getEmail()); + requireActivity().startActivity(authenticityCredentialsIntent); break; default: break; diff --git a/app/src/main/res/layout/bottom_sheet_node_item.xml b/app/src/main/res/layout/bottom_sheet_node_item.xml index 6ba8fd5b779..1dac0060f93 100644 --- a/app/src/main/res/layout/bottom_sheet_node_item.xml +++ b/app/src/main/res/layout/bottom_sheet_node_item.xml @@ -211,10 +211,12 @@ + android:background="@color/grey_012_white_012" + android:orientation="horizontal" /> Date: Wed, 28 Dec 2022 22:06:26 +0530 Subject: [PATCH 052/334] Un-necessary change removed --- .../modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 3d12fe86310..08b2d7d1dfd 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -324,7 +324,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } if (isOnline(requireContext())) { - if(!searchViewModel.getMandatoryFingerPrintVerificationState().getValue()) { + if(searchViewModel.getMandatoryFingerPrintVerificationState().getValue()) { ////TODO This flag for false for now. This will get manipulated after SDK changes showOwnerSharedFolder(); TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); From 5466543e1b1bce70db4c99aa69417fe19acbeb2d Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 30 Dec 2022 08:44:14 +0530 Subject: [PATCH 053/334] AND-15313 - IncomingSharesViewModel modified to append the unverified node list with the existing node list --- .../incoming/IncomingSharesViewModel.kt | 20 ++++++++++++++++--- .../incoming/model/IncomingSharesState.kt | 4 ++-- .../incoming/IncomingSharesViewModelTest.kt | 19 ++++++++++++------ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 50b899df385..e0591b82d28 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.AuthorizeNode +import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates @@ -38,6 +39,7 @@ class IncomingSharesViewModel @Inject constructor( monitorNodeUpdates: MonitorNodeUpdates, private val getFeatureFlagValue: GetFeatureFlagValue, private val getUnverifiedInComingShares: GetUnverifiedIncomingShares, + private val getChildrenNode: GetChildrenNode, ) : ViewModel() { /** private UI state */ @@ -72,9 +74,21 @@ class IncomingSharesViewModel @Inject constructor( } viewModelScope.launch { - isMandatoryFingerprintRequired() - _state.update { - it.copy(unVerifiedInComingShares = getUnverifiedInComingShares(_state.value.sortOrder)) + getUnverifiedInComingShares(_state.value.sortOrder).forEach { shareData -> + if (shareData.nodeHandle != -1L || shareData.nodeHandle != INVALID_HANDLE) { + getNodeByHandle(shareData.nodeHandle)?.let { megaNode -> + val unverifiedNodes = getChildrenNode(megaNode, _state.value.sortOrder) + if (unverifiedNodes.isNotEmpty()) { + _state.update { + it.copy(nodes = _state.value.nodes + unverifiedNodes) + } + } + } + } else { + _state.update { + it.copy(unVerifiedIncomingNodes = emptyList()) + } + } } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index 38caa5d6334..3baf88523a9 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -14,8 +14,8 @@ import nz.mega.sdk.MegaNode * @param isInvalidHandle true if parent handle is invalid * @param isLoading true if the nodes are loading * @param sortOrder current sort order - * @param unVerifiedInComingShares number of unverified incoming shares * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed + * @param unVerifiedIncomingNodes List of unverified Incoming [MegaNode] */ data class IncomingSharesState( val incomingHandle: Long = -1L, @@ -25,8 +25,8 @@ data class IncomingSharesState( val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val unVerifiedInComingShares: List = emptyList(), val isMandatoryFingerprintVerificationNeeded: Boolean = false, + val unVerifiedIncomingNodes: List = emptyList(), ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 2b7a7a4b989..e13201fad92 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.AuthorizeNode +import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.featuretoggle.AppFeatures @@ -65,6 +66,8 @@ class IncomingSharesViewModelTest { private val getUnverifiedInComingShares = mock() + private val getChildrenNode = mock() + @Before fun setUp() { Dispatchers.setMain(UnconfinedTestDispatcher()) @@ -82,6 +85,7 @@ class IncomingSharesViewModelTest { monitorNodeUpdates, getFeatureFlagValue, getUnverifiedInComingShares, + getChildrenNode, ) } @@ -95,7 +99,6 @@ class IncomingSharesViewModelTest { assertThat(initial.isInvalidHandle).isEqualTo(true) assertThat(initial.incomingParentHandle).isEqualTo(null) assertThat(initial.sortOrder).isEqualTo(SortOrder.ORDER_NONE) - assertThat(initial.unVerifiedInComingShares).isEmpty() } } @@ -521,11 +524,15 @@ class IncomingSharesViewModelTest { @Test fun `test that unverified incoming shares are returned`() = runTest { val shareData = ShareData("user", 8766L, 0, 987654678L, true) - whenever(getUnverifiedInComingShares(underTest.state.value.sortOrder)).thenReturn(listOf( - shareData)) - initViewModel() - underTest.state.test { - assertThat(awaitItem().unVerifiedInComingShares).isNotEmpty() + whenever(getUnverifiedInComingShares(underTest.state.value.sortOrder)) + .thenReturn(listOf(shareData)) + val node1 = mock() + val node2 = mock() + val expected = listOf(node1, node2) + whenever(getNodeByHandle(any())).thenReturn(mock()) + whenever(getChildrenNode(any(), any())).thenReturn(expected) + underTest.state.map { it.nodes }.distinctUntilChanged().test { + assertThat(awaitItem().size).isEqualTo(2) } } } \ No newline at end of file From f3f83f7fa7a01e8d931bc3acd8fd13dc0a00999b Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Sun, 1 Jan 2023 19:40:47 +0530 Subject: [PATCH 054/334] AND-15313 MegaNodeAdapter list updated with Unverified nodes --- .../incoming/IncomingSharesViewModel.kt | 21 +++++++------------ .../outgoing/OutgoingSharesViewModel.kt | 11 +++++++++- .../outgoing/model/OutgoingSharesState.kt | 4 ++-- .../incoming/IncomingSharesViewModelTest.kt | 13 ++---------- .../outgoing/OutgoingSharesViewModelTest.kt | 12 ++++++----- 5 files changed, 29 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index e0591b82d28..3d8abbe5976 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.AuthorizeNode -import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates @@ -39,7 +38,6 @@ class IncomingSharesViewModel @Inject constructor( monitorNodeUpdates: MonitorNodeUpdates, private val getFeatureFlagValue: GetFeatureFlagValue, private val getUnverifiedInComingShares: GetUnverifiedIncomingShares, - private val getChildrenNode: GetChildrenNode, ) : ViewModel() { /** private UI state */ @@ -51,6 +49,8 @@ class IncomingSharesViewModel @Inject constructor( /** stack of scroll position for each depth */ private val lastPositionStack: Stack = Stack() + private val unverifiedIncomingNodes = mutableListOf() + init { viewModelScope.launch { refreshNodes()?.let { setNodes(it) } @@ -74,22 +74,17 @@ class IncomingSharesViewModel @Inject constructor( } viewModelScope.launch { + isMandatoryFingerprintRequired() getUnverifiedInComingShares(_state.value.sortOrder).forEach { shareData -> - if (shareData.nodeHandle != -1L || shareData.nodeHandle != INVALID_HANDLE) { + if (!isInvalidHandle(shareData.nodeHandle)) { getNodeByHandle(shareData.nodeHandle)?.let { megaNode -> - val unverifiedNodes = getChildrenNode(megaNode, _state.value.sortOrder) - if (unverifiedNodes.isNotEmpty()) { - _state.update { - it.copy(nodes = _state.value.nodes + unverifiedNodes) - } - } - } - } else { - _state.update { - it.copy(unVerifiedIncomingNodes = emptyList()) + unverifiedIncomingNodes.add(megaNode) } } } + _state.update { + it.copy(nodes = _state.value.nodes + unverifiedIncomingNodes) + } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index a301d30e2fe..a7b1e932f5d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -47,6 +47,8 @@ class OutgoingSharesViewModel @Inject constructor( /** stack of scroll position for each depth */ private val lastPositionStack: Stack = Stack() + private val unverifiedOutgoingNodes = mutableListOf() + init { viewModelScope.launch { refreshNodes()?.let { setNodes(it) } @@ -58,8 +60,15 @@ class OutgoingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() + getUnverifiedOutgoingShares(_state.value.sortOrder).forEach { shareData -> + if (!isInvalidHandle(shareData.nodeHandle)) { + getNodeByHandle(shareData.nodeHandle)?.let { megaNode -> + unverifiedOutgoingNodes.add(megaNode) + } + } + } _state.update { - it.copy(unVerifiedOutGoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder)) + it.copy(nodes = _state.value.nodes + unverifiedOutgoingNodes) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index 69f31bfebd8..d692f78a7ce 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -14,8 +14,8 @@ import nz.mega.sdk.MegaNode * @param isInvalidHandle true if handle is invalid * @param isLoading true if the nodes are loading * @param sortOrder current sort order - * @param unVerifiedOutGoingShares number of unverified outgoing shares * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed + * @param unVerifiedOutGoingNodes List of Unverified outgoing [MegaNode] */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -25,8 +25,8 @@ data class OutgoingSharesState( val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val unVerifiedOutGoingShares: List = emptyList(), val isMandatoryFingerprintVerificationNeeded: Boolean = false, + val unVerifiedOutGoingNodes: List = emptyList(), ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index e13201fad92..668af68b81f 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -11,7 +11,6 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.AuthorizeNode -import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.featuretoggle.AppFeatures @@ -66,8 +65,6 @@ class IncomingSharesViewModelTest { private val getUnverifiedInComingShares = mock() - private val getChildrenNode = mock() - @Before fun setUp() { Dispatchers.setMain(UnconfinedTestDispatcher()) @@ -85,7 +82,6 @@ class IncomingSharesViewModelTest { monitorNodeUpdates, getFeatureFlagValue, getUnverifiedInComingShares, - getChildrenNode, ) } @@ -527,12 +523,7 @@ class IncomingSharesViewModelTest { whenever(getUnverifiedInComingShares(underTest.state.value.sortOrder)) .thenReturn(listOf(shareData)) val node1 = mock() - val node2 = mock() - val expected = listOf(node1, node2) - whenever(getNodeByHandle(any())).thenReturn(mock()) - whenever(getChildrenNode(any(), any())).thenReturn(expected) - underTest.state.map { it.nodes }.distinctUntilChanged().test { - assertThat(awaitItem().size).isEqualTo(2) - } + whenever(getNodeByHandle(any())).thenReturn(node1) + assertThat(getNodeByHandle(any())).isNotNull() } } \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index 9acc4133d7c..2e5f0bee893 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -445,10 +445,12 @@ class OutgoingSharesViewModelTest { } @Test - fun `test that unverified incoming shares are returned`() = runTest { - initViewModel() - underTest.state.test { - assertThat(awaitItem().unVerifiedOutGoingShares).isNotEmpty() - } + fun `test that unverified outgoing shares are returned`() = runTest { + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + whenever(getUnverifiedOutgoingShares(underTest.state.value.sortOrder)) + .thenReturn(listOf(shareData)) + val node1 = mock() + whenever(getNodeByHandle(any())).thenReturn(node1) + assertThat(getNodeByHandle(any())).isNotNull() } } From 16b8a74efb7ba0820a69c33a8e8240850807f704 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 2 Jan 2023 18:06:49 +0530 Subject: [PATCH 055/334] MegaNodeAdapter modified to display unverified items count --- .../android/app/main/ManagerActivity.java | 399 ++++++++++++++++-- .../app/main/adapters/MegaNodeAdapter.java | 31 +- .../shares/incoming/IncomingSharesFragment.kt | 2 +- .../incoming/IncomingSharesViewModel.kt | 11 + .../incoming/model/IncomingSharesState.kt | 2 + .../shares/outgoing/OutgoingSharesFragment.kt | 2 +- .../outgoing/OutgoingSharesViewModel.kt | 10 + .../outgoing/model/OutgoingSharesState.kt | 2 + 8 files changed, 423 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 9ed0603efc8..b0b8424e9b3 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -16,12 +16,15 @@ import static mega.privacy.android.app.constants.BroadcastConstants.INVALID_ACTION; import static mega.privacy.android.app.constants.EventConstants.EVENT_CALL_ON_HOLD_CHANGE; import static mega.privacy.android.app.constants.EventConstants.EVENT_CALL_STATUS_CHANGE; +import static mega.privacy.android.app.constants.EventConstants.EVENT_FAILED_TRANSFERS; import static mega.privacy.android.app.constants.EventConstants.EVENT_FINISH_ACTIVITY; import static mega.privacy.android.app.constants.EventConstants.EVENT_REFRESH; import static mega.privacy.android.app.constants.EventConstants.EVENT_REFRESH_PHONE_NUMBER; import static mega.privacy.android.app.constants.EventConstants.EVENT_SESSION_ON_HOLD_CHANGE; +import static mega.privacy.android.app.constants.EventConstants.EVENT_TRANSFER_OVER_QUOTA; import static mega.privacy.android.app.constants.EventConstants.EVENT_UPDATE_VIEW_MODE; import static mega.privacy.android.app.constants.EventConstants.EVENT_USER_EMAIL_UPDATED; +import static mega.privacy.android.app.constants.EventConstants.EVENT_USER_NAME_UPDATED; import static mega.privacy.android.app.constants.IntentConstants.ACTION_OPEN_ACHIEVEMENTS; import static mega.privacy.android.app.constants.IntentConstants.EXTRA_ACCOUNT_TYPE; import static mega.privacy.android.app.constants.IntentConstants.EXTRA_ASK_PERMISSIONS; @@ -93,6 +96,7 @@ import static mega.privacy.android.app.utils.JobUtil.fireCameraUploadJob; import static mega.privacy.android.app.utils.JobUtil.fireCancelCameraUploadJob; import static mega.privacy.android.app.utils.JobUtil.fireStopCameraUploadJob; +import static mega.privacy.android.app.utils.JobUtil.stopCameraUploadSyncHeartbeatWorkers; import static mega.privacy.android.app.utils.MegaApiUtils.calculateDeepBrowserTreeIncoming; import static mega.privacy.android.app.utils.MegaNodeDialogUtil.ACTION_BACKUP_FAB; import static mega.privacy.android.app.utils.MegaNodeDialogUtil.ACTION_BACKUP_SHARE_FOLDER; @@ -116,6 +120,7 @@ import static mega.privacy.android.app.utils.OfflineUtils.saveOffline; import static mega.privacy.android.app.utils.StringResourcesUtils.getQuantityString; import static mega.privacy.android.app.utils.TextUtil.isTextEmpty; +import static mega.privacy.android.app.utils.TimeUtils.getHumanizedTime; import static mega.privacy.android.app.utils.UploadUtil.chooseFiles; import static mega.privacy.android.app.utils.UploadUtil.chooseFolder; import static mega.privacy.android.app.utils.UploadUtil.getFolder; @@ -213,6 +218,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SearchView; import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; @@ -292,6 +298,7 @@ import mega.privacy.android.app.fragments.homepage.documents.DocumentsFragment; import mega.privacy.android.app.fragments.homepage.main.HomepageFragment; import mega.privacy.android.app.fragments.homepage.main.HomepageFragmentDirections; +import mega.privacy.android.app.fragments.managerFragments.cu.CustomHideBottomViewOnScrollBehaviour; import mega.privacy.android.app.fragments.offline.OfflineFragment; import mega.privacy.android.app.fragments.recent.RecentsBucketFragment; import mega.privacy.android.app.fragments.settingsFragments.cookie.CookieDialogHandler; @@ -317,6 +324,7 @@ import mega.privacy.android.app.main.listeners.CreateGroupChatWithPublicLink; import mega.privacy.android.app.main.listeners.FabButtonListener; import mega.privacy.android.app.main.managerSections.CompletedTransfersFragment; +import mega.privacy.android.app.main.managerSections.NotificationsFragment; import mega.privacy.android.app.main.managerSections.TransfersFragment; import mega.privacy.android.app.main.managerSections.TurnOnNotificationsFragment; import mega.privacy.android.app.main.megachat.BadgeDrawerArrowDrawable; @@ -386,6 +394,7 @@ import mega.privacy.android.app.upgradeAccount.UpgradeAccountActivity; import mega.privacy.android.app.usecase.CopyNodeUseCase; import mega.privacy.android.app.usecase.DownloadNodeUseCase; +import mega.privacy.android.app.usecase.GetNodeUseCase; import mega.privacy.android.app.usecase.MoveNodeUseCase; import mega.privacy.android.app.usecase.RemoveNodeUseCase; import mega.privacy.android.app.usecase.UploadUseCase; @@ -530,6 +539,8 @@ public class ManagerActivity extends TransfersManagementActivity @Inject RemoveNodeUseCase removeNodeUseCase; @Inject + GetNodeUseCase getNodeUseCase; + @Inject GetChatChangesUseCase getChatChangesUseCase; @Inject DownloadNodeUseCase downloadNodeUseCase; @@ -601,8 +612,14 @@ public class ManagerActivity extends TransfersManagementActivity MegaNode parentNodeManager; public DrawerLayout drawerLayout; + ArrayList contacts = new ArrayList<>(); + ArrayList visibleContacts = new ArrayList<>(); public boolean openFolderRefresh = false; + + public boolean openSettingsStartScreen; + public boolean openSettingsStorage = false; + public boolean openSettingsQR = false; boolean newAccount = false; public boolean newCreationAccount; @@ -612,6 +629,8 @@ public class ManagerActivity extends TransfersManagementActivity private boolean isStorageStatusDialogShown = false; + private boolean isTransferOverQuotaWarningShown; + private AlertDialog transferOverQuotaWarning; private AlertDialog confirmationTransfersDialog; private AlertDialog reconnectDialog; @@ -633,6 +652,56 @@ public class ManagerActivity extends TransfersManagementActivity private boolean isInAlbumContent; public boolean fromAlbumContent = false; + public enum FragmentTag { + CLOUD_DRIVE, HOMEPAGE, PHOTOS, INBOX, INCOMING_SHARES, OUTGOING_SHARES, SEARCH, TRANSFERS, COMPLETED_TRANSFERS, + RECENT_CHAT, RUBBISH_BIN, NOTIFICATIONS, TURN_ON_NOTIFICATIONS, PERMISSIONS, SMS_VERIFICATION, + LINKS, MEDIA_DISCOVERY, ALBUM_CONTENT, PHOTOS_FILTER; + + public String getTag() { + switch (this) { + case CLOUD_DRIVE: + return "fileBrowserFragment"; + case HOMEPAGE: + return "homepageFragment"; + case RUBBISH_BIN: + return "rubbishBinFragment"; + case PHOTOS: + return "photosFragment"; + case INBOX: + return "inboxFragment"; + case INCOMING_SHARES: + return "incomingSharesFragment"; + case OUTGOING_SHARES: + return "outgoingSharesFragment"; + case SEARCH: + return "searchFragment"; + case TRANSFERS: + return "android:switcher:" + R.id.transfers_tabs_pager + ":" + 0; + case COMPLETED_TRANSFERS: + return "android:switcher:" + R.id.transfers_tabs_pager + ":" + 1; + case RECENT_CHAT: + return "chatTabsFragment"; + case NOTIFICATIONS: + return "notificationsFragment"; + case TURN_ON_NOTIFICATIONS: + return "turnOnNotificationsFragment"; + case PERMISSIONS: + return "permissionsFragment"; + case SMS_VERIFICATION: + return "smsVerificationFragment"; + case LINKS: + return "linksFragment"; + case MEDIA_DISCOVERY: + return "mediaDiscoveryFragment"; + case ALBUM_CONTENT: + return "fragmentAlbumContent"; + case PHOTOS_FILTER: + return "fragmentPhotosFilter"; + } + return null; + } + } + public boolean turnOnNotifications = false; private DrawerItem drawerItem; @@ -666,6 +735,8 @@ public class ManagerActivity extends TransfersManagementActivity private RelativeLayout callInProgressLayout; private Chronometer callInProgressChrono; private TextView callInProgressText; + private LinearLayout microOffLayout; + private LinearLayout videoOnLayout; boolean firstTimeAfterInstallation = true; SearchView searchView; @@ -682,6 +753,11 @@ public class ManagerActivity extends TransfersManagementActivity private HomepageScreen mHomepageScreen = HomepageScreen.HOMEPAGE; + private enum HomepageScreen { + HOMEPAGE, IMAGES, FAVOURITES, DOCUMENTS, AUDIO, VIDEO, + FULLSCREEN_OFFLINE, OFFLINE_FILE_INFO, RECENT_BUCKET + } + public boolean isList = true; private String pathNavigationOffline; @@ -700,6 +776,7 @@ public class ManagerActivity extends TransfersManagementActivity private Fragment albumContentFragment; private PhotosFilterFragment photosFilterFragment; private ChatTabsFragment chatTabsFragment; + private NotificationsFragment notificationsFragment; private TurnOnNotificationsFragment turnOnNotificationsFragment; private PermissionsFragment permissionsFragment; private SMSVerificationFragment smsVerificationFragment; @@ -715,12 +792,17 @@ public class ManagerActivity extends TransfersManagementActivity private AlertDialog permissionsDialog; private AlertDialog presenceStatusDialog; + private AlertDialog alertNotPermissionsUpload; + private AlertDialog clearRubbishBinDialog; + private AlertDialog insertPassDialog; + private AlertDialog changeUserAttributeDialog; private AlertDialog alertDialogStorageStatus; private AlertDialog alertDialogSMSVerification; private AlertDialog newTextFileDialog; private AlertDialog newFolderDialog; private MenuItem searchMenuItem; + private MenuItem enableSelectMenuItem; private MenuItem doNotDisturbMenuItem; private MenuItem clearRubbishBinMenuitem; private MenuItem cancelAllTransfersMenuItem; @@ -728,6 +810,7 @@ public class ManagerActivity extends TransfersManagementActivity private MenuItem pauseTransfersMenuIcon; private MenuItem retryTransfers; private MenuItem clearCompletedTransfers; + private MenuItem scanQRcodeMenuItem; private MenuItem returnCallMenuItem; private MenuItem openLinkMenuItem; private Chronometer chronometerMenuItem; @@ -739,6 +822,8 @@ public class ManagerActivity extends TransfersManagementActivity Button enable2FAButton; Button skip2FAButton; + private boolean is2FAEnabled = false; + public boolean comesFromNotifications = false; public int comesFromNotificationsLevel = 0; public long comesFromNotificationHandle = INVALID_VALUE; @@ -835,6 +920,8 @@ public void onChanged(Boolean aBoolean) { private final ArrayList fabs = new ArrayList<>(); // end for Meeting + // Backup warning dialog + private AlertDialog backupWarningDialog; private ArrayList backupHandleList; private int backupDialogType = BACKUP_DIALOG_SHOW_NONE; private Long backupNodeHandle; @@ -1123,6 +1210,35 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis break; } + case REQUEST_CAMERA_UPLOAD: + case REQUEST_CAMERA_ON_OFF: + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + checkIfShouldShowBusinessCUAlert(); + } else { + stopCameraUploadSyncHeartbeatWorkers(this); + showSnackbar(SNACKBAR_TYPE, getString(R.string.on_refuse_storage_permission), INVALID_HANDLE); + } + + break; + + case REQUEST_CAMERA_ON_OFF_FIRST_TIME: + if (permissions.length == 0) { + return; + } + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + checkIfShouldShowBusinessCUAlert(); + } else { + if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[0])) { + if (getPhotosFragment() != null) { + photosFragment.onStoragePermissionRefused(); + } + } else { + showSnackbar(SNACKBAR_TYPE, getString(R.string.on_refuse_storage_permission), INVALID_HANDLE); + } + } + + break; + case PERMISSIONS_FRAGMENT: { if (getPermissionsFragment() != null) { permissionsFragment.setNextPermission(); @@ -1206,6 +1322,7 @@ public void onSaveInstanceState(Bundle outState) { outState.putBoolean(BUSINESS_CU_ALERT_SHOWN, isBusinessCUAlertShown); } + outState.putBoolean(TRANSFER_OVER_QUOTA_SHOWN, isTransferOverQuotaWarningShown); outState.putInt(TYPE_CALL_PERMISSION, typesCameraPermission); outState.putBoolean(JOINING_CHAT_LINK, joiningToChatLink); outState.putString(LINK_JOINING_CHAT_LINK, linkJoinToChatLink); @@ -1372,6 +1489,7 @@ protected void onCreate(Bundle savedInstanceState) { openLinkDialogIsShown = savedInstanceState.getBoolean(OPEN_LINK_DIALOG_SHOWN, false); isBusinessGraceAlertShown = savedInstanceState.getBoolean(BUSINESS_GRACE_ALERT_SHOWN, false); isBusinessCUAlertShown = savedInstanceState.getBoolean(BUSINESS_CU_ALERT_SHOWN, false); + isTransferOverQuotaWarningShown = savedInstanceState.getBoolean(TRANSFER_OVER_QUOTA_SHOWN, false); typesCameraPermission = savedInstanceState.getInt(TYPE_CALL_PERMISSION, INVALID_TYPE_PERMISSIONS); joiningToChatLink = savedInstanceState.getBoolean(JOINING_CHAT_LINK, false); linkJoinToChatLink = savedInstanceState.getString(LINK_JOINING_CHAT_LINK); @@ -1433,6 +1551,17 @@ protected void onCreate(Bundle savedInstanceState) { LiveEventBus.get(EVENT_REFRESH_PHONE_NUMBER, Boolean.class) .observeForever(refreshAddPhoneNumberButtonObserver); + LiveEventBus.get(EVENT_TRANSFER_OVER_QUOTA, Boolean.class).observe(this, update -> { + updateTransfersWidget(TransferType.NONE); + showTransfersTransferOverQuotaWarning(); + }); + + LiveEventBus.get(EVENT_FAILED_TRANSFERS, Boolean.class).observe(this, failed -> { + if (drawerItem == DrawerItem.TRANSFERS && getTabItemTransfers() == TransfersTab.COMPLETED_TAB) { + retryTransfers.setVisible(failed); + } + }); + registerReceiver(transferFinishReceiver, new IntentFilter(BROADCAST_ACTION_TRANSFER_FINISH)); LiveEventBus.get(EVENT_CALL_STATUS_CHANGE, MegaChatCall.class).observe(this, callStatusObserver); @@ -1512,18 +1641,28 @@ protected void onCreate(Bundle savedInstanceState) { prefs = dbH.getPreferences(); if (prefs == null) { firstTimeAfterInstallation = true; + isList = true; } else { if (prefs.getFirstTime() == null) { firstTimeAfterInstallation = true; } else { firstTimeAfterInstallation = Boolean.parseBoolean(prefs.getFirstTime()); } + if (prefs.getPreferredViewList() == null) { + isList = true; + } else { + isList = Boolean.parseBoolean(prefs.getPreferredViewList()); + } } if (firstTimeAfterInstallation) { setStartScreenTimeStamp(this); } + Timber.d("Preferred View List: %s", isList); + + LiveEventBus.get(EVENT_LIST_GRID_CHANGE, Boolean.class).post(isList); + handler = new Handler(); Timber.d("Set view"); @@ -1821,6 +1960,8 @@ public void onPageScrollStateChanged(int state) { callInProgressLayout.setOnClickListener(this); callInProgressChrono = findViewById(R.id.call_in_progress_chrono); callInProgressText = findViewById(R.id.call_in_progress_text); + microOffLayout = findViewById(R.id.micro_off_layout); + videoOnLayout = findViewById(R.id.video_on_layout); callInProgressLayout.setVisibility(View.GONE); if (mElevationCause > 0) { @@ -2120,7 +2261,7 @@ public void onPageScrollStateChanged(int state) { selectDrawerItemPending = false; } else if (fragmentHandle == megaApi.getRubbishNode().getHandle()) { drawerItem = DrawerItem.RUBBISH_BIN; - rubbishBinViewModel.setRubbishBinHandle(handleIntent); + viewModel.setRubbishBinParentHandle(handleIntent); selectDrawerItem(drawerItem); selectDrawerItemPending = false; } else if (fragmentHandle == megaApi.getInboxNode().getHandle()) { @@ -2423,6 +2564,10 @@ public void onPageScrollStateChanged(int state) { } } + if (drawerItem == DrawerItem.TRANSFERS && isTransferOverQuotaWarningShown) { + showTransfersTransferOverQuotaWarning(); + } + PsaManager.INSTANCE.startChecking(); if (savedInstanceState != null && savedInstanceState.getBoolean(IS_NEW_TEXT_FILE_SHOWN, false)) { @@ -2616,6 +2761,32 @@ private void showBusinessGraceAlert() { isBusinessGraceAlertShown = true; } + /** + * If the account is business and not a master user, it shows a warning. + * Otherwise proceeds to enable CU. + */ + public void checkIfShouldShowBusinessCUAlert() { + if (isBusinessAccount() && !megaApi.isMasterBusinessAccount()) { + showBusinessCUAlert(); + } else { + enableCUClicked(); + } + } + + + /** + * Proceeds to enable CU action. + */ + private void enableCUClicked() { + if (getPhotosFragment() != null) { + if (photosFragment.isEnablePhotosViewShown()) { + photosFragment.enableCameraUpload(); + } else { + photosFragment.enableCameraUploadClick(); + } + } + } + /** * Shows a warning to business users about the risks of enabling CU. */ @@ -2988,7 +3159,7 @@ void actionOpenFolder(long handleIntent) { default: if (megaApi.isInRubbish(parentIntentN)) { - rubbishBinViewModel.setRubbishBinHandle(handleIntent); + viewModel.setRubbishBinParentHandle(handleIntent); drawerItem = DrawerItem.RUBBISH_BIN; } else if (megaApi.isInInbox(parentIntentN)) { inboxViewModel.updateInboxHandle(handleIntent); @@ -3293,6 +3464,7 @@ protected void onPostResume() { } case NOTIFICATIONS: break; + } case HOMEPAGE: default: setBottomNavigationMenuItemChecked(HOME_BNV); @@ -3403,6 +3575,8 @@ protected void onDestroy() { LiveEventBus.get(EVENT_FINISH_ACTIVITY, Boolean.class).removeObserver(finishObserver); LiveEventBus.get(EVENT_FAB_CHANGE, Boolean.class).removeObserver(fabChangeObserver); + destroyPayments(); + cancelSearch(); if (reconnectDialog != null) { reconnectDialog.cancel(); @@ -3617,12 +3791,12 @@ public void setToolbarTitle() { } case RUBBISH_BIN: { aB.setSubtitle(null); - MegaNode node = megaApi.getNodeByHandle(rubbishBinState(ManagerActivity.this).getRubbishBinHandle()); + MegaNode node = megaApi.getNodeByHandle(viewModel.getState().getValue().getRubbishBinParentHandle()); MegaNode rubbishNode = megaApi.getRubbishNode(); if (rubbishNode == null) { - rubbishBinViewModel.setRubbishBinHandle(INVALID_HANDLE); + viewModel.setRubbishBinParentHandle(INVALID_HANDLE); viewModel.setIsFirstNavigationLevel(true); - } else if (rubbishBinState(ManagerActivity.this).getRubbishBinHandle() == INVALID_HANDLE || node == null || node.getHandle() == rubbishNode.getHandle()) { + } else if (viewModel.getState().getValue().getRubbishBinParentHandle() == INVALID_HANDLE || node == null || node.getHandle() == rubbishNode.getHandle()) { aB.setTitle(StringResourcesUtils.getString(R.string.section_rubbish_bin)); viewModel.setIsFirstNavigationLevel(true); } else { @@ -4072,28 +4246,31 @@ public void selectDrawerItemSharedItems() { } }).attach(); - if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded()) { - //// TODO hardcoded number for now. This will get changed after SDK changes are available - TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); - if (incomingSharesTab != null) { - incomingSharesTab.getOrCreateBadge().setNumber(2); - } - } - - if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded()) { - TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); - if (outgoingSharesTab != null) { - outgoingSharesTab.getOrCreateBadge().setNumber(2); + ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { + if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { + TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); + if (incomingSharesTab != null) { + int incomingSharesCount = incomingSharesState.getUnVerifiedIncomingNodesCount(); + if (incomingSharesCount > 0) { + incomingSharesTab.getOrCreateBadge().setNumber(incomingSharesCount); + } + } } - } + return Unit.INSTANCE; + }); - if (linksViewModel.getMandatoryFingerPrintVerificationState().getValue()) { - TabLayout.Tab linksTab = tabLayoutShares.getTabAt(2); - if (linksTab != null) { - linksTab.getOrCreateBadge().setNumber(1); + ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { + if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { + TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); + if (outgoingSharesTab != null) { + int outgoingSharesCount = outgoingSharesState.getUnVerifiedOutGoingNodesCount(); + if (outgoingSharesCount > 0) { + outgoingSharesTab.getOrCreateBadge().setNumber(outgoingSharesCount); + } + } } - } - + return Unit.INSTANCE; + }); } updateSharesTab(); @@ -4400,6 +4577,7 @@ private void resetCUFragment() { cuViewTypes.setVisibility(View.GONE); if (getPhotosFragment() != null) { + photosFragment.setDefaultView(); showBottomView(); } } @@ -4529,6 +4707,7 @@ public void selectDrawerItem(DrawerItem item) { supportInvalidateOptionsMenu(); showFabButton(); showHideBottomNavigationView(false); + refreshCUNodes(); if (!comesFromNotifications) { bottomNavigationCurrentItem = PHOTOS_BNV; } @@ -4603,6 +4782,14 @@ public void selectDrawerItem(DrawerItem item) { } case CHAT: { Timber.d("Chat selected"); + if (megaApi != null) { + contacts = megaApi.getContacts(); + for (int i = 0; i < contacts.size(); i++) { + if (contacts.get(i).getVisibility() == MegaUser.VISIBILITY_VISIBLE) { + visibleContacts.add(contacts.get(i)); + } + } + } selectDrawerItemChat(); supportInvalidateOptionsMenu(); showHideBottomNavigationView(false); @@ -4787,6 +4974,12 @@ public void checkScrollElevation() { } break; } + case PHOTOS: { + if (getPhotosFragment() != null) { + photosFragment.checkScroll(); + } + break; + } case INBOX: { inboxFragment = (InboxFragment) getSupportFragmentManager().findFragmentByTag(FragmentTag.INBOX.getTag()); if (inboxFragment != null) { @@ -5109,7 +5302,7 @@ public boolean onQueryTextChange(String newText) { searchViewModel.setSearchQuery(newText); searchViewModel.performSearch( fileBrowserState(ManagerActivity.this).getFileBrowserHandle(), - rubbishBinState(ManagerActivity.this).getRubbishBinHandle(), + viewModel.getState().getValue().getRubbishBinParentHandle(), inboxState(ManagerActivity.this).getInboxHandle(), incomingSharesState(ManagerActivity.this).getIncomingHandle(), outgoingSharesState(ManagerActivity.this).getOutgoingHandle(), @@ -5130,6 +5323,7 @@ public boolean onQueryTextChange(String newText) { retryTransfers = menu.findItem(R.id.action_menu_retry_transfers); playTransfersMenuIcon = menu.findItem(R.id.action_play); pauseTransfersMenuIcon = menu.findItem(R.id.action_pause); + scanQRcodeMenuItem = menu.findItem(R.id.action_scan_qr); returnCallMenuItem = menu.findItem(R.id.action_return_call); RelativeLayout rootView = (RelativeLayout) returnCallMenuItem.getActionView(); layoutCallMenuItem = rootView.findViewById(R.id.layout_menu_call); @@ -6039,7 +6233,7 @@ private void proceedWithRestoration(List nodes) { .subscribe((result, throwable) -> { if (throwable == null) { boolean notValidView = result.isSingleAction() && result.isSuccess() - && rubbishBinState(ManagerActivity.this).getRubbishBinHandle() == nodes.get(0).getHandle(); + && viewModel.getState().getValue().getRubbishBinParentHandle() == nodes.get(0).getHandle(); showRestorationOrRemovalResult(notValidView, result.getResultText()); } else if (throwable instanceof ForeignNodeException) { @@ -6058,6 +6252,7 @@ private void showRestorationOrRemovalResult(boolean notValidView, String message if (notValidView) { rubbishBinViewModel.setRubbishBinHandle(INVALID_HANDLE); setToolbarTitle(); + refreshRubbishBin(); } dismissAlertDialogIfExists(statusDialog); @@ -7145,6 +7340,12 @@ public void cameraUploadsClicked() { selectDrawerItem(drawerItem); } + public void skipInitialCUSetup() { + viewModel.setIsFirstLogin(false); + drawerItem = getStartDrawerItem(); + selectDrawerItem(drawerItem); + } + /** * Refresh the UI of the Photos feature */ @@ -7161,6 +7362,27 @@ public void refreshPhotosFragment() { } } + /** + * Checks if should update some cu view visibility. + * + * @param visibility New requested visibility update. + * @return True if should apply the visibility update, false otherwise. + */ + private boolean rightCUVisibilityChange(int visibility) { + return drawerItem == DrawerItem.PHOTOS || visibility == View.GONE; + } + + /** + * Updates cuViewTypes view visibility. + * + * @param visibility New visibility value to set. + */ + public void updateCUViewTypes(int visibility) { + if (rightCUVisibilityChange(visibility)) { + cuViewTypes.setVisibility(visibility); + } + } + /** * Shows the bottom sheet to manage a completed transfer. * @@ -7413,6 +7635,8 @@ public void refreshCloudDrive() { if (comesFromNotificationChildNodeHandleList == null) { fileBrowserFragment.hideMultipleSelect(); } + fileBrowserFragment.setNodes(nodes); + fileBrowserFragment.getRecyclerView().invalidate(); } } @@ -7446,6 +7670,12 @@ public void refreshOthersOrder() { refreshSearch(); } + public void refreshCUNodes() { + if (getPhotosFragment() != null) { + photosFragment.loadPhotos(); + } + } + public void setFirstNavigationLevel(boolean firstNavigationLevel) { Timber.d("Set value to: %s", firstNavigationLevel); viewModel.setIsFirstNavigationLevel(firstNavigationLevel); @@ -9113,6 +9343,8 @@ public void onRequestFinish(MegaApiJava api, MegaRequest request, MegaError e) { Timber.d("Attribute USER_ATTR_GEOLOCATION disabled"); MegaApplication.setEnabledGeoLocation(false); } + } else if (request.getParamType() == MegaApiJava.USER_ATTR_DISABLE_VERSIONS) { + MegaApplication.setDisableFileVersions(request.getFlag()); } } else if (request.getType() == MegaRequest.TYPE_GET_CANCEL_LINK) { Timber.d("TYPE_GET_CANCEL_LINK"); @@ -9345,6 +9577,19 @@ public void onRequestFinish(MegaApiJava api, MegaRequest request, MegaError e) { } } + /** + * Updates own firstName/lastName and fullName data in UI and DB. + * + * @param firstName True if the update makes reference to the firstName, false it to the lastName. + * @param newName New firstName/lastName text. + * @param e MegaError of the request. + */ + private void updateMyData(boolean firstName, String newName, MegaError e) { + myAccountInfo.updateMyData(firstName, newName, e); + updateUserNameNavigationView(myAccountInfo.getFullName()); + LiveEventBus.get(EVENT_USER_NAME_UPDATED, Boolean.class).post(true); + } + @Override public void onRequestTemporaryError(MegaApiJava api, MegaRequest request, MegaError e) { @@ -9485,6 +9730,10 @@ public void openLocation(long nodeHandle, long[] childNodeHandleList) { public void updateUserAlerts(List userAlerts) { viewModel.checkNumUnreadUserAlerts(UnreadUserAlertsCheckType.NOTIFICATIONS_TITLE_AND_TOOLBAR_ICON); + notificationsFragment = (NotificationsFragment) getSupportFragmentManager().findFragmentByTag(FragmentTag.NOTIFICATIONS.getTag()); + if (notificationsFragment != null && userAlerts != null) { + notificationsFragment.updateNotifications(userAlerts); + } } public void updateMyEmail(String email) { @@ -9836,6 +10085,14 @@ public boolean isList() { return isList; } + public void setList(boolean isList) { + this.isList = isList; + } + + public boolean isListCameraUploads() { + return false; + } + public boolean getFirstLogin() { return viewModel.getState().getValue().isFirstLogin(); } @@ -10268,7 +10525,7 @@ public void setChatBadge() { } public void setPendingActionsBadge() { - if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded()) { + if (viewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded()) { sharedItemsView.addView(pendingActionsBadge); ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.chat_badge_text); @@ -10314,6 +10571,31 @@ public void refreshMenu() { supportInvalidateOptionsMenu(); } + public boolean is2FAEnabled() { + return is2FAEnabled; + } + + /** + * Sets or removes the layout behaviour to hide the bottom view when scrolling. + * + * @param enable True if should set the behaviour, false if should remove it. + */ + public void enableHideBottomViewOnScroll(boolean enable) { + LinearLayout layout = findViewById(R.id.container_bottom); + if (layout == null || isInImagesPage()) { + return; + } + + final CoordinatorLayout.LayoutParams fParams + = new CoordinatorLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + fParams.setMargins(0, 0, 0, enable ? 0 : getResources().getDimensionPixelSize(R.dimen.bottom_navigation_view_height)); + fragmentLayout.setLayoutParams(fParams); + + CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) layout.getLayoutParams(); + params.setBehavior(enable ? new CustomHideBottomViewOnScrollBehaviour() : null); + layout.setLayoutParams(params); + } + /** * Shows all the content of bottom view. */ @@ -10328,6 +10610,33 @@ public void showBottomView() { .start(); } + /** + * Shows or hides the bottom view and animates the transition. + * + * @param hide True if should hide it, false if should show it. + */ + public void animateBottomView(boolean hide) { + LinearLayout bottomView = findViewById(R.id.container_bottom); + if (bottomView == null || fragmentLayout == null) { + return; + } + + CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) fragmentLayout.getLayoutParams(); + + if (hide && bottomView.getVisibility() == View.VISIBLE) { + bottomView.animate().translationY(bottomView.getHeight()).setDuration(ANIMATION_DURATION) + .withStartAction(() -> params.bottomMargin = 0) + .withEndAction(() -> bottomView.setVisibility(View.GONE)).start(); + } else if (!hide && bottomView.getVisibility() == View.GONE) { + int bottomMargin = getResources().getDimensionPixelSize(R.dimen.bottom_navigation_view_height); + + bottomView.animate().translationY(0).setDuration(ANIMATION_DURATION) + .withStartAction(() -> bottomView.setVisibility(View.VISIBLE)) + .withEndAction(() -> params.bottomMargin = bottomMargin) + .start(); + } + } + public void showHideBottomNavigationView(boolean hide) { if (bNV == null) return; @@ -10643,6 +10952,29 @@ public void viewNodeInFolder(MegaNode node) { } } + /** + * Shows a "transfer over quota" warning. + */ + public void showTransfersTransferOverQuotaWarning() { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); + int messageResource = R.string.warning_transfer_over_quota; + + transferOverQuotaWarning = builder.setTitle(R.string.label_transfer_over_quota) + .setMessage(getString(messageResource, getHumanizedTime(megaApi.getBandwidthOverquotaDelay()))) + .setPositiveButton(R.string.my_account_upgrade_pro, (dialog, which) -> { + navigateToUpgradeAccount(); + }) + .setNegativeButton(R.string.general_dismiss, null) + .setCancelable(false) + .setOnDismissListener(dialog -> isTransferOverQuotaWarningShown = false) + .create(); + + transferOverQuotaWarning.setCanceledOnTouchOutside(false); + TimeUtils.createAndShowCountDownTimer(messageResource, transferOverQuotaWarning); + transferOverQuotaWarning.show(); + isTransferOverQuotaWarningShown = true; + } + /** * Updates the position of the transfers widget. * @@ -10755,6 +11087,10 @@ private PermissionsFragment getPermissionsFragment() { return permissionsFragment = (PermissionsFragment) getSupportFragmentManager().findFragmentByTag(FragmentTag.PERMISSIONS.getTag()); } + public Fragment getMDFragment() { + return mediaDiscoveryFragment; + } + public Fragment getAlbumContentFragment() { return albumContentFragment; } @@ -10906,6 +11242,15 @@ public boolean isInPhotosPage() { return drawerItem == DrawerItem.PHOTOS; } + /** + * Checks if the current screen is Media discovery page. + * + * @return True if the current screen is Media discovery page, false otherwise. + */ + public boolean isInMDPage() { + return drawerItem == DrawerItem.CLOUD_DRIVE && isInMDMode; + } + /** * Create the instance of FileBackupManager */ diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index c7324979cba..7e55306ed4e 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -92,8 +92,10 @@ import mega.privacy.android.app.presentation.rubbishbin.RubbishBinFragment; import mega.privacy.android.app.presentation.search.SearchFragment; import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesFragment; +import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel; import mega.privacy.android.app.presentation.shares.links.LinksFragment; import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesFragment; +import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel; import mega.privacy.android.app.utils.ColorUtils; import mega.privacy.android.app.utils.MegaNodeUtil; import mega.privacy.android.app.utils.NodeTakenDownDialogListener; @@ -143,6 +145,8 @@ public class MegaNodeAdapter extends RecyclerView.Adapter = unverifiedIncomingNodes } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index 3baf88523a9..d555602e62d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -16,6 +16,7 @@ import nz.mega.sdk.MegaNode * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unVerifiedIncomingNodes List of unverified Incoming [MegaNode] + * @param unVerifiedIncomingNodesCount unVerifiedIncomingNodesCount to display on tab */ data class IncomingSharesState( val incomingHandle: Long = -1L, @@ -27,6 +28,7 @@ data class IncomingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unVerifiedIncomingNodes: List = emptyList(), + val unVerifiedIncomingNodesCount: Int = 0, ) { /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 20702cdfdb2..23a0031f0c5 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -269,7 +269,7 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { adapter?.parentHandle = state().outgoingHandle adapter?.setListFragment(recyclerView) } - + adapter?.setOutgoingSharesViewModel(viewModel) if (managerActivity?.isList == false) gridLayoutManager?.spanSizeLookup = gridLayoutManager?.spanCount?.let { adapter?.getSpanSizeLookup(it) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index a7b1e932f5d..822bbc2c863 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -72,6 +72,11 @@ class OutgoingSharesViewModel @Inject constructor( } } + viewModelScope.launch { + _state.update { + it.copy(unVerifiedOutGoingNodesCount = unverifiedOutgoingNodes.size) + } + } } /** @@ -203,4 +208,9 @@ class OutgoingSharesViewModel @Inject constructor( it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) } } + + /** + * Get the unverified outgoing nodes list to check in [MegaNodeAdapter] + */ + fun getOutgoingUnverifiedNodes(): List = unverifiedOutgoingNodes } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index d692f78a7ce..30a8fdf14dc 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -16,6 +16,7 @@ import nz.mega.sdk.MegaNode * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unVerifiedOutGoingNodes List of Unverified outgoing [MegaNode] + * @param unVerifiedOutGoingNodesCount unVerifiedOutGoingNodesCount to display on tab */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -27,6 +28,7 @@ data class OutgoingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unVerifiedOutGoingNodes: List = emptyList(), + val unVerifiedOutGoingNodesCount: Int = 0, ) { /** From c711337545533051f495b883569a3cc796e5ec38 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 2 Jan 2023 18:29:42 +0530 Subject: [PATCH 056/334] order of nodes changed --- .../app/presentation/shares/incoming/IncomingSharesViewModel.kt | 2 +- .../app/presentation/shares/outgoing/OutgoingSharesViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 27b2ab028d1..0be70525d9d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -83,7 +83,7 @@ class IncomingSharesViewModel @Inject constructor( } } _state.update { - it.copy(nodes = _state.value.nodes + unverifiedIncomingNodes) + it.copy(nodes = unverifiedIncomingNodes + _state.value.nodes) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 822bbc2c863..a00126fd7f8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -68,7 +68,7 @@ class OutgoingSharesViewModel @Inject constructor( } } _state.update { - it.copy(nodes = _state.value.nodes + unverifiedOutgoingNodes) + it.copy(nodes = unverifiedOutgoingNodes + _state.value.nodes) } } From 3fe4ae2a409229adba85f7dfa6c41eda72767681 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 2 Jan 2023 20:28:31 +0530 Subject: [PATCH 057/334] Minor code optimisation --- .../android/app/main/ManagerActivity.java | 12 ++++++------ .../app/main/adapters/MegaNodeAdapter.java | 16 ++++++++-------- .../shares/incoming/IncomingSharesFragment.kt | 3 ++- .../shares/incoming/IncomingSharesViewModel.kt | 3 +-- .../shares/outgoing/OutgoingSharesFragment.kt | 4 ++-- .../shares/outgoing/OutgoingSharesViewModel.kt | 2 +- .../outgoing/OutgoingSharesViewModelTest.kt | 3 --- 7 files changed, 20 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index b0b8424e9b3..b5e0001284a 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -4250,9 +4250,9 @@ public void selectDrawerItemSharedItems() { if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); if (incomingSharesTab != null) { - int incomingSharesCount = incomingSharesState.getUnVerifiedIncomingNodesCount(); - if (incomingSharesCount > 0) { - incomingSharesTab.getOrCreateBadge().setNumber(incomingSharesCount); + int incomingNodesCount = incomingSharesState.getUnVerifiedIncomingNodesCount(); + if (incomingNodesCount > 0) { + incomingSharesTab.getOrCreateBadge().setNumber(incomingNodesCount); } } } @@ -4263,9 +4263,9 @@ public void selectDrawerItemSharedItems() { if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); if (outgoingSharesTab != null) { - int outgoingSharesCount = outgoingSharesState.getUnVerifiedOutGoingNodesCount(); - if (outgoingSharesCount > 0) { - outgoingSharesTab.getOrCreateBadge().setNumber(outgoingSharesCount); + int outgoingNodesCount = outgoingSharesState.getUnVerifiedOutGoingNodesCount(); + if (outgoingNodesCount > 0) { + outgoingSharesTab.getOrCreateBadge().setNumber(outgoingNodesCount); } } } diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 7e55306ed4e..f4db93972e9 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -145,8 +145,8 @@ public class MegaNodeAdapter extends RecyclerView.Adapter unverifiedIncomingNodes; + private List unverifiedOutgoingNodes; private Boolean isMandatoryFingerprintVerificationNeeded; public static class ViewHolderBrowser extends RecyclerView.ViewHolder { @@ -1091,7 +1091,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if(isMandatoryFingerprintVerificationNeeded && incomingSharesViewModel.getIncomingUnverifiedNodes().contains(node.getHandle())) { + if(isMandatoryFingerprintVerificationNeeded && unverifiedIncomingNodes.contains(node.getHandle())) { holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } @@ -1104,7 +1104,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - if(isMandatoryFingerprintVerificationNeeded && outgoingSharesViewModel.getOutgoingUnverifiedNodes().contains(node.getHandle())) { + if(isMandatoryFingerprintVerificationNeeded && unverifiedOutgoingNodes.contains(node.getHandle())) { holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } @@ -1523,11 +1523,11 @@ public void setMandatoryFingerprintVerificationValue(boolean isVerificationNeede this.isMandatoryFingerprintVerificationNeeded = isVerificationNeeded; } - public void setIncomingSharesViewModel(IncomingSharesViewModel viewModel) { - this.incomingSharesViewModel = viewModel; + public void setUnverifiedIncomingNodes(List nodes) { + this.unverifiedIncomingNodes = nodes; } - public void setOutgoingSharesViewModel(OutgoingSharesViewModel viewModel) { - this.outgoingSharesViewModel = viewModel; + public void setUnverifiedOutgoingNodes(List nodes) { + this.unverifiedOutgoingNodes = nodes; } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 1a53cae7218..95cdc2e39ac 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -65,6 +65,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { else getGridView(inflater, container) initAdapter() + observe() selectNewlyAddedNodes() return view @@ -275,7 +276,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { adapter?.parentHandle = state().incomingHandle adapter?.setListFragment(recyclerView) } - adapter?.setIncomingSharesViewModel(viewModel) + adapter?.setUnverifiedIncomingNodes(viewModel.getUnverifiedIncomingNodes()) if (managerActivity?.isList == false) gridLayoutManager?.spanSizeLookup = gridLayoutManager?.let { adapter?.getSpanSizeLookup(it.spanCount) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 0be70525d9d..0443f096c5b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -86,7 +86,6 @@ class IncomingSharesViewModel @Inject constructor( it.copy(nodes = unverifiedIncomingNodes + _state.value.nodes) } } - viewModelScope.launch { _state.update { it.copy(unVerifiedIncomingNodesCount = unverifiedIncomingNodes.size) @@ -228,5 +227,5 @@ class IncomingSharesViewModel @Inject constructor( /** * Get the unverified incoming nodes list to check in [MegaNodeAdapter] */ - fun getIncomingUnverifiedNodes(): List = unverifiedIncomingNodes + fun getUnverifiedIncomingNodes(): List = unverifiedIncomingNodes } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 23a0031f0c5..09044f98574 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -234,6 +234,7 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) + } } } @@ -269,14 +270,13 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { adapter?.parentHandle = state().outgoingHandle adapter?.setListFragment(recyclerView) } - adapter?.setOutgoingSharesViewModel(viewModel) + adapter?.setUnverifiedOutgoingNodes(viewModel.getUnverifiedOutgoingNodes()) if (managerActivity?.isList == false) gridLayoutManager?.spanSizeLookup = gridLayoutManager?.spanCount?.let { adapter?.getSpanSizeLookup(it) } adapter?.isMultipleSelect = false recyclerView?.adapter = adapter - adapter?.setMandatoryFingerprintVerificationValue(viewModel.state.value.isMandatoryFingerprintVerificationNeeded) } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index a00126fd7f8..75b1cfb284f 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -212,5 +212,5 @@ class OutgoingSharesViewModel @Inject constructor( /** * Get the unverified outgoing nodes list to check in [MegaNodeAdapter] */ - fun getOutgoingUnverifiedNodes(): List = unverifiedOutgoingNodes + fun getUnverifiedOutgoingNodes(): List = unverifiedOutgoingNodes } \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index 2e5f0bee893..88753e78df5 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -446,9 +446,6 @@ class OutgoingSharesViewModelTest { @Test fun `test that unverified outgoing shares are returned`() = runTest { - val shareData = ShareData("user", 8766L, 0, 987654678L, true) - whenever(getUnverifiedOutgoingShares(underTest.state.value.sortOrder)) - .thenReturn(listOf(shareData)) val node1 = mock() whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() From b0c2d7cf2d5d1522663a22481c85eb95bdf4fb60 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 3 Jan 2023 11:03:56 +0530 Subject: [PATCH 058/334] Removed unnecessary additions in Ui state --- .../android/app/main/ManagerActivity.java | 65 +++++++++++-------- .../app/main/adapters/MegaNodeAdapter.java | 17 +++-- .../shares/incoming/IncomingSharesFragment.kt | 4 +- .../incoming/IncomingSharesViewModel.kt | 20 +----- .../incoming/model/IncomingSharesState.kt | 2 - .../shares/outgoing/OutgoingSharesFragment.kt | 4 +- .../outgoing/OutgoingSharesViewModel.kt | 23 ++----- .../outgoing/model/OutgoingSharesState.kt | 2 - 8 files changed, 60 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index b5e0001284a..210dad1a390 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -2597,6 +2597,9 @@ public void onPageScrollStateChanged(int state) { } else { Timber.d("Backup warning dialog is not show"); } + + addUnverifiedIncomingCountBadge(); + addUnverifiedOutgoingCountBadge(); } @@ -4245,32 +4248,6 @@ public void selectDrawerItemSharedItems() { tab.setIcon(R.drawable.link_ic); } }).attach(); - - ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { - if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { - TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); - if (incomingSharesTab != null) { - int incomingNodesCount = incomingSharesState.getUnVerifiedIncomingNodesCount(); - if (incomingNodesCount > 0) { - incomingSharesTab.getOrCreateBadge().setNumber(incomingNodesCount); - } - } - } - return Unit.INSTANCE; - }); - - ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { - if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { - TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); - if (outgoingSharesTab != null) { - int outgoingNodesCount = outgoingSharesState.getUnVerifiedOutGoingNodesCount(); - if (outgoingNodesCount > 0) { - outgoingSharesTab.getOrCreateBadge().setNumber(outgoingNodesCount); - } - } - } - return Unit.INSTANCE; - }); } updateSharesTab(); @@ -11432,4 +11409,40 @@ private void closeDrawer() { private boolean isBusinessAccount() { return megaApi.isBusinessAccount() && myAccountInfo.getAccountType() == BUSINESS; } + + /** + * Function to add unverified incoming count on tabs + */ + private void addUnverifiedIncomingCountBadge() { + ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { + if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { + TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); + if (incomingSharesTab != null) { + int incomingNodesCount = incomingSharesState.getUnVerifiedIncomingNodes().size(); + if (incomingNodesCount > 0) { + incomingSharesTab.getOrCreateBadge().setNumber(incomingNodesCount); + } + } + } + return Unit.INSTANCE; + }); + } + + /** + * Function to add unverified outgoing count on tabs + */ + private void addUnverifiedOutgoingCountBadge() { + ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { + if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { + TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); + if (outgoingSharesTab != null) { + int outgoingNodesCount = outgoingSharesState.getUnVerifiedOutGoingNodes().size(); + if (outgoingNodesCount > 0) { + outgoingSharesTab.getOrCreateBadge().setNumber(outgoingNodesCount); + } + } + } + return Unit.INSTANCE; + }); + } } diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index f4db93972e9..2a6fb89e355 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1091,9 +1091,11 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if(isMandatoryFingerprintVerificationNeeded && unverifiedIncomingNodes.contains(node.getHandle())) { - holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); - holder.permissionsIcon.setImageResource(R.drawable.serious_warning); + if (isMandatoryFingerprintVerificationNeeded) { + if (unverifiedIncomingNodes.get(position).getHandle() == node.getHandle()) { + holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); + holder.permissionsIcon.setImageResource(R.drawable.serious_warning); + } } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { @@ -1103,10 +1105,11 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else if (type == OUTGOING_SHARES_ADAPTER) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - - if(isMandatoryFingerprintVerificationNeeded && unverifiedOutgoingNodes.contains(node.getHandle())) { - holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); - holder.permissionsIcon.setImageResource(R.drawable.serious_warning); + if (isMandatoryFingerprintVerificationNeeded) { + if (unverifiedOutgoingNodes.get(position).getHandle() == node.getHandle()) { + holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); + holder.permissionsIcon.setImageResource(R.drawable.serious_warning); + } } } } else { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 95cdc2e39ac..bb6dd75f566 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -241,6 +241,8 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) + adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) + adapter?.setUnverifiedIncomingNodes(it.unVerifiedIncomingNodes) } } } @@ -276,14 +278,12 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { adapter?.parentHandle = state().incomingHandle adapter?.setListFragment(recyclerView) } - adapter?.setUnverifiedIncomingNodes(viewModel.getUnverifiedIncomingNodes()) if (managerActivity?.isList == false) gridLayoutManager?.spanSizeLookup = gridLayoutManager?.let { adapter?.getSpanSizeLookup(it.spanCount) } adapter?.isMultipleSelect = false recyclerView?.adapter = adapter - adapter?.setMandatoryFingerprintVerificationValue(viewModel.state.value.isMandatoryFingerprintVerificationNeeded) } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 0443f096c5b..f8d2dfbf6d1 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -75,22 +75,13 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() - getUnverifiedInComingShares(_state.value.sortOrder).forEach { shareData -> - if (!isInvalidHandle(shareData.nodeHandle)) { - getNodeByHandle(shareData.nodeHandle)?.let { megaNode -> - unverifiedIncomingNodes.add(megaNode) - } - } - } + val unverifiedIncomingNodes = getUnverifiedInComingShares(_state.value.sortOrder) + .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { it.copy(nodes = unverifiedIncomingNodes + _state.value.nodes) } } - viewModelScope.launch { - _state.update { - it.copy(unVerifiedIncomingNodesCount = unverifiedIncomingNodes.size) - } - } } @@ -223,9 +214,4 @@ class IncomingSharesViewModel @Inject constructor( it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) } } - - /** - * Get the unverified incoming nodes list to check in [MegaNodeAdapter] - */ - fun getUnverifiedIncomingNodes(): List = unverifiedIncomingNodes } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index d555602e62d..3baf88523a9 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -16,7 +16,6 @@ import nz.mega.sdk.MegaNode * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unVerifiedIncomingNodes List of unverified Incoming [MegaNode] - * @param unVerifiedIncomingNodesCount unVerifiedIncomingNodesCount to display on tab */ data class IncomingSharesState( val incomingHandle: Long = -1L, @@ -28,7 +27,6 @@ data class IncomingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unVerifiedIncomingNodes: List = emptyList(), - val unVerifiedIncomingNodesCount: Int = 0, ) { /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 09044f98574..6457ca645c4 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -234,7 +234,8 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) - + adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) + adapter?.setUnverifiedOutgoingNodes(it.unVerifiedOutGoingNodes) } } } @@ -270,7 +271,6 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { adapter?.parentHandle = state().outgoingHandle adapter?.setListFragment(recyclerView) } - adapter?.setUnverifiedOutgoingNodes(viewModel.getUnverifiedOutgoingNodes()) if (managerActivity?.isList == false) gridLayoutManager?.spanSizeLookup = gridLayoutManager?.spanCount?.let { adapter?.getSpanSizeLookup(it) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 75b1cfb284f..866aab619d8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -60,21 +60,11 @@ class OutgoingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() - getUnverifiedOutgoingShares(_state.value.sortOrder).forEach { shareData -> - if (!isInvalidHandle(shareData.nodeHandle)) { - getNodeByHandle(shareData.nodeHandle)?.let { megaNode -> - unverifiedOutgoingNodes.add(megaNode) - } - } - } + val unverifiedOutGoingNodes = getUnverifiedOutgoingShares(_state.value.sortOrder) + .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { - it.copy(nodes = unverifiedOutgoingNodes + _state.value.nodes) - } - } - - viewModelScope.launch { - _state.update { - it.copy(unVerifiedOutGoingNodesCount = unverifiedOutgoingNodes.size) + it.copy(nodes = unverifiedOutGoingNodes + _state.value.nodes) } } } @@ -208,9 +198,4 @@ class OutgoingSharesViewModel @Inject constructor( it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) } } - - /** - * Get the unverified outgoing nodes list to check in [MegaNodeAdapter] - */ - fun getUnverifiedOutgoingNodes(): List = unverifiedOutgoingNodes } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index 30a8fdf14dc..d692f78a7ce 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -16,7 +16,6 @@ import nz.mega.sdk.MegaNode * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unVerifiedOutGoingNodes List of Unverified outgoing [MegaNode] - * @param unVerifiedOutGoingNodesCount unVerifiedOutGoingNodesCount to display on tab */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -28,7 +27,6 @@ data class OutgoingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unVerifiedOutGoingNodes: List = emptyList(), - val unVerifiedOutGoingNodesCount: Int = 0, ) { /** From 5c17cd84cf8b197face1d4b00393c311ae0d295c Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 3 Jan 2023 11:08:39 +0530 Subject: [PATCH 059/334] empty check added to list --- .../privacy/android/app/main/adapters/MegaNodeAdapter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 2a6fb89e355..5f9dd1763ee 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1092,7 +1092,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } if (isMandatoryFingerprintVerificationNeeded) { - if (unverifiedIncomingNodes.get(position).getHandle() == node.getHandle()) { + if (!unverifiedIncomingNodes.isEmpty() && unverifiedIncomingNodes.get(position).getHandle() == node.getHandle()) { holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } @@ -1106,7 +1106,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); if (isMandatoryFingerprintVerificationNeeded) { - if (unverifiedOutgoingNodes.get(position).getHandle() == node.getHandle()) { + if (!unverifiedOutgoingNodes.isEmpty() && unverifiedOutgoingNodes.get(position).getHandle() == node.getHandle()) { holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } From b717e2ad77f55aa934543926e44dddb0241319ae Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 3 Jan 2023 14:45:49 +0530 Subject: [PATCH 060/334] Unit test updated --- .../incoming/IncomingSharesViewModelTest.kt | 15 ++++++++++----- .../outgoing/OutgoingSharesViewModelTest.kt | 7 ++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 668af68b81f..6e899118fc5 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -63,7 +63,10 @@ class IncomingSharesViewModelTest { }.thenReturn(true) } - private val getUnverifiedInComingShares = mock() + private val getUnverifiedInComingShares = mock { + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) + } @Before fun setUp() { @@ -518,12 +521,14 @@ class IncomingSharesViewModelTest { } @Test - fun `test that unverified incoming shares are returned`() = runTest { - val shareData = ShareData("user", 8766L, 0, 987654678L, true) - whenever(getUnverifiedInComingShares(underTest.state.value.sortOrder)) - .thenReturn(listOf(shareData)) + fun `test that unverified outgoing shares are returned`() = runTest { val node1 = mock() whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() + initViewModel() + underTest.state.map { it.nodes }.distinctUntilChanged() + .test { + assertThat(awaitItem().size).isEqualTo(1) + } } } \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index 88753e78df5..a7f98d26b2f 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -60,7 +60,7 @@ class OutgoingSharesViewModelTest { }.thenReturn(true) } - private val getUnverifiedOutgoingShares = mock() { + private val getUnverifiedOutgoingShares = mock { val shareData = ShareData("user", 8766L, 0, 987654678L, true) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } @@ -449,5 +449,10 @@ class OutgoingSharesViewModelTest { val node1 = mock() whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() + initViewModel() + underTest.state.map { it.nodes }.distinctUntilChanged() + .test { + assertThat(awaitItem().size).isEqualTo(1) + } } } From d49c827f9f67cdbc0868b31fbff15d7430b3f39c Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 3 Jan 2023 15:43:45 +0530 Subject: [PATCH 061/334] Code review comments resolved Unit test cases modified accordingly --- .../java/mega/privacy/android/app/main/ManagerActivity.java | 2 +- .../presentation/shares/incoming/IncomingSharesFragment.kt | 3 +-- .../presentation/shares/incoming/IncomingSharesViewModel.kt | 4 +--- .../presentation/shares/outgoing/OutgoingSharesFragment.kt | 4 ++-- .../presentation/shares/outgoing/OutgoingSharesViewModel.kt | 6 ++---- .../shares/outgoing/model/OutgoingSharesState.kt | 4 ++-- .../shares/incoming/IncomingSharesViewModelTest.kt | 2 +- .../shares/outgoing/OutgoingSharesViewModelTest.kt | 2 +- 8 files changed, 11 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 210dad1a390..75ae291523c 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -11436,7 +11436,7 @@ private void addUnverifiedOutgoingCountBadge() { if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); if (outgoingSharesTab != null) { - int outgoingNodesCount = outgoingSharesState.getUnVerifiedOutGoingNodes().size(); + int outgoingNodesCount = outgoingSharesState.getUnVerifiedOutgoingNodes().size(); if (outgoingNodesCount > 0) { outgoingSharesTab.getOrCreateBadge().setNumber(outgoingNodesCount); } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index bb6dd75f566..9d62608e24c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -230,8 +230,6 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { hideTabs(true) return@collect } - - updateNodes(it.nodes) hideTabs(!it.isFirstNavigationLevel()) managerActivity?.showFabButton() @@ -243,6 +241,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) adapter?.setUnverifiedIncomingNodes(it.unVerifiedIncomingNodes) + updateNodes(it.unVerifiedIncomingNodes + it.nodes) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index f8d2dfbf6d1..bd4d2f27092 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -49,8 +49,6 @@ class IncomingSharesViewModel @Inject constructor( /** stack of scroll position for each depth */ private val lastPositionStack: Stack = Stack() - private val unverifiedIncomingNodes = mutableListOf() - init { viewModelScope.launch { refreshNodes()?.let { setNodes(it) } @@ -79,7 +77,7 @@ class IncomingSharesViewModel @Inject constructor( .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { - it.copy(nodes = unverifiedIncomingNodes + _state.value.nodes) + it.copy(unVerifiedIncomingNodes = unverifiedIncomingNodes) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 6457ca645c4..3ab104c61fc 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -224,7 +224,6 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { return@collect } - updateNodes(it.nodes) hideTabs(!it.isFirstNavigationLevel()) managerActivity?.showFabButton() @@ -235,7 +234,8 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { hideActionMode() setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) - adapter?.setUnverifiedOutgoingNodes(it.unVerifiedOutGoingNodes) + adapter?.setUnverifiedOutgoingNodes(it.unVerifiedOutgoingNodes) + updateNodes(it.unVerifiedOutgoingNodes + it.nodes) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 866aab619d8..bc76d911b88 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -47,8 +47,6 @@ class OutgoingSharesViewModel @Inject constructor( /** stack of scroll position for each depth */ private val lastPositionStack: Stack = Stack() - private val unverifiedOutgoingNodes = mutableListOf() - init { viewModelScope.launch { refreshNodes()?.let { setNodes(it) } @@ -60,11 +58,11 @@ class OutgoingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() - val unverifiedOutGoingNodes = getUnverifiedOutgoingShares(_state.value.sortOrder) + val unverifiedOutgoingNodes = getUnverifiedOutgoingShares(_state.value.sortOrder) .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { - it.copy(nodes = unverifiedOutGoingNodes + _state.value.nodes) + it.copy(unVerifiedOutgoingNodes = unverifiedOutgoingNodes) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index d692f78a7ce..b9b121326e1 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -15,7 +15,7 @@ import nz.mega.sdk.MegaNode * @param isLoading true if the nodes are loading * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed - * @param unVerifiedOutGoingNodes List of Unverified outgoing [MegaNode] + * @param unVerifiedOutgoingNodes List of Unverified outgoing [MegaNode] */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -26,7 +26,7 @@ data class OutgoingSharesState( val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, - val unVerifiedOutGoingNodes: List = emptyList(), + val unVerifiedOutgoingNodes: List = emptyList(), ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 6e899118fc5..07faa706a77 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -526,7 +526,7 @@ class IncomingSharesViewModelTest { whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() initViewModel() - underTest.state.map { it.nodes }.distinctUntilChanged() + underTest.state.map { it.unVerifiedIncomingNodes }.distinctUntilChanged() .test { assertThat(awaitItem().size).isEqualTo(1) } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index a7f98d26b2f..f7bfb4d5a09 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -450,7 +450,7 @@ class OutgoingSharesViewModelTest { whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() initViewModel() - underTest.state.map { it.nodes }.distinctUntilChanged() + underTest.state.map { it.unVerifiedOutgoingNodes }.distinctUntilChanged() .test { assertThat(awaitItem().size).isEqualTo(1) } From 77018d39e5c9802c552140c573c8e0eec9d410d8 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 3 Jan 2023 18:04:44 +0530 Subject: [PATCH 062/334] Unverified node handle checked with adapter current node & UI items added accordingly --- .../app/main/adapters/MegaNodeAdapter.java | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 5f9dd1763ee..b67b3ec855f 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -92,10 +92,8 @@ import mega.privacy.android.app.presentation.rubbishbin.RubbishBinFragment; import mega.privacy.android.app.presentation.search.SearchFragment; import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesFragment; -import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel; import mega.privacy.android.app.presentation.shares.links.LinksFragment; import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesFragment; -import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel; import mega.privacy.android.app.utils.ColorUtils; import mega.privacy.android.app.utils.MegaNodeUtil; import mega.privacy.android.app.utils.NodeTakenDownDialogListener; @@ -1091,11 +1089,8 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if (isMandatoryFingerprintVerificationNeeded) { - if (!unverifiedIncomingNodes.isEmpty() && unverifiedIncomingNodes.get(position).getHandle() == node.getHandle()) { - holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); - holder.permissionsIcon.setImageResource(R.drawable.serious_warning); - } + if (isMandatoryFingerprintVerificationNeeded && !unverifiedIncomingNodes.isEmpty()) { + showUnverifiedNodeUi(unverifiedIncomingNodes, node, holder); } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { @@ -1105,11 +1100,8 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else if (type == OUTGOING_SHARES_ADAPTER) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - if (isMandatoryFingerprintVerificationNeeded) { - if (!unverifiedOutgoingNodes.isEmpty() && unverifiedOutgoingNodes.get(position).getHandle() == node.getHandle()) { - holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); - holder.permissionsIcon.setImageResource(R.drawable.serious_warning); - } + if (isMandatoryFingerprintVerificationNeeded && !unverifiedOutgoingNodes.isEmpty()) { + showUnverifiedNodeUi(unverifiedOutgoingNodes, node, holder); } } } else { @@ -1533,4 +1525,21 @@ public void setUnverifiedIncomingNodes(List nodes) { public void setUnverifiedOutgoingNodes(List nodes) { this.unverifiedOutgoingNodes = nodes; } + + /** + * Function to check if current node is unverified & show Ui items accordingly + * + * @param unverifiedNodes Unverified Nodes List + * @param adapterNode Current node from adapter + * @param holder [ViewHolderBrowserList] + */ + private void showUnverifiedNodeUi(List unverifiedNodes, MegaNode adapterNode, ViewHolderBrowserList holder) { + for (int i = 0; i < unverifiedNodes.size(); i++) { + if (unverifiedNodes.get(i).getHandle() == adapterNode.getHandle()) { + holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); + holder.permissionsIcon.setImageResource(R.drawable.serious_warning); + break; + } + } + } } From 8291b2839c211c7c06f4d029e15681f3ed525d6c Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 3 Jan 2023 18:54:08 +0530 Subject: [PATCH 063/334] variable name modified --- .../privacy/android/app/main/adapters/MegaNodeAdapter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index b67b3ec855f..f2473217d7f 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1530,12 +1530,12 @@ public void setUnverifiedOutgoingNodes(List nodes) { * Function to check if current node is unverified & show Ui items accordingly * * @param unverifiedNodes Unverified Nodes List - * @param adapterNode Current node from adapter + * @param currentNode Current node from adapter * @param holder [ViewHolderBrowserList] */ - private void showUnverifiedNodeUi(List unverifiedNodes, MegaNode adapterNode, ViewHolderBrowserList holder) { + private void showUnverifiedNodeUi(List unverifiedNodes, MegaNode currentNode, ViewHolderBrowserList holder) { for (int i = 0; i < unverifiedNodes.size(); i++) { - if (unverifiedNodes.get(i).getHandle() == adapterNode.getHandle()) { + if (unverifiedNodes.get(i).getHandle() == currentNode.getHandle()) { holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); break; From cf5a90d0c0d55f38c032eee8e162bd7b5aba12cf Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 4 Jan 2023 09:33:34 +0530 Subject: [PATCH 064/334] Code optimised --- .../android/app/main/ManagerActivity.java | 51 +++++++++---------- .../app/main/adapters/MegaNodeAdapter.java | 40 +++++++++------ 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 75ae291523c..6d83e2f353e 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -2597,9 +2597,20 @@ public void onPageScrollStateChanged(int state) { } else { Timber.d("Backup warning dialog is not show"); } + ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { + if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { + addUnverifiedIncomingCountBadge(incomingSharesState.getUnVerifiedIncomingNodes().size()); + } + return Unit.INSTANCE; + }); + + ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { + if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { + addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnVerifiedOutgoingNodes().size()); + } + return Unit.INSTANCE; + }); - addUnverifiedIncomingCountBadge(); - addUnverifiedOutgoingCountBadge(); } @@ -11413,36 +11424,24 @@ private boolean isBusinessAccount() { /** * Function to add unverified incoming count on tabs */ - private void addUnverifiedIncomingCountBadge() { - ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { - if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { - TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); - if (incomingSharesTab != null) { - int incomingNodesCount = incomingSharesState.getUnVerifiedIncomingNodes().size(); - if (incomingNodesCount > 0) { - incomingSharesTab.getOrCreateBadge().setNumber(incomingNodesCount); - } - } + private void addUnverifiedIncomingCountBadge(int unverifiedNodesCount) { + TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); + if (incomingSharesTab != null) { + if (unverifiedNodesCount > 0) { + incomingSharesTab.getOrCreateBadge().setNumber(unverifiedNodesCount); } - return Unit.INSTANCE; - }); + } } /** * Function to add unverified outgoing count on tabs */ - private void addUnverifiedOutgoingCountBadge() { - ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { - if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { - TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); - if (outgoingSharesTab != null) { - int outgoingNodesCount = outgoingSharesState.getUnVerifiedOutgoingNodes().size(); - if (outgoingNodesCount > 0) { - outgoingSharesTab.getOrCreateBadge().setNumber(outgoingNodesCount); - } - } + private void addUnverifiedOutgoingCountBadge(int unverifiedNodesCount) { + TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); + if (outgoingSharesTab != null) { + if (unverifiedNodesCount > 0) { + outgoingSharesTab.getOrCreateBadge().setNumber(unverifiedNodesCount); } - return Unit.INSTANCE; - }); + } } } diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index f2473217d7f..bbddf9a4917 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -69,6 +69,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Set; import mega.privacy.android.app.MegaApplication; import mega.privacy.android.app.MimeTypeList; @@ -143,8 +144,8 @@ public class MegaNodeAdapter extends RecyclerView.Adapter unverifiedIncomingNodes; - private List unverifiedOutgoingNodes; + private Set unverifiedIncomingNodeHandles; + private Set unverifiedOutgoingNodeHandles; private Boolean isMandatoryFingerprintVerificationNeeded; public static class ViewHolderBrowser extends RecyclerView.ViewHolder { @@ -1089,8 +1090,8 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if (isMandatoryFingerprintVerificationNeeded && !unverifiedIncomingNodes.isEmpty()) { - showUnverifiedNodeUi(unverifiedIncomingNodes, node, holder); + if (isMandatoryFingerprintVerificationNeeded && !unverifiedIncomingNodeHandles.isEmpty()) { + showUnverifiedNodeUi(unverifiedIncomingNodeHandles, node, holder); } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { @@ -1100,8 +1101,8 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else if (type == OUTGOING_SHARES_ADAPTER) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - if (isMandatoryFingerprintVerificationNeeded && !unverifiedOutgoingNodes.isEmpty()) { - showUnverifiedNodeUi(unverifiedOutgoingNodes, node, holder); + if (isMandatoryFingerprintVerificationNeeded && !unverifiedOutgoingNodeHandles.isEmpty()) { + showUnverifiedNodeUi(unverifiedOutgoingNodeHandles, node, holder); } } } else { @@ -1518,12 +1519,24 @@ public void setMandatoryFingerprintVerificationValue(boolean isVerificationNeede this.isMandatoryFingerprintVerificationNeeded = isVerificationNeeded; } + /** + * Adds unverified incoming nodes to Set + * + * @param nodes - List of incoming [MegaNode] + */ public void setUnverifiedIncomingNodes(List nodes) { - this.unverifiedIncomingNodes = nodes; + unverifiedIncomingNodeHandles.clear(); + nodes.forEach(megaNode -> unverifiedIncomingNodeHandles.add(megaNode.getHandle())); } + /** + * Adds unverified outgoing nodes to Set + * + * @param nodes - List of outgoing [MegaNode] + */ public void setUnverifiedOutgoingNodes(List nodes) { - this.unverifiedOutgoingNodes = nodes; + unverifiedOutgoingNodeHandles.clear(); + nodes.forEach(megaNode -> unverifiedOutgoingNodeHandles.add(megaNode.getHandle())); } /** @@ -1533,13 +1546,10 @@ public void setUnverifiedOutgoingNodes(List nodes) { * @param currentNode Current node from adapter * @param holder [ViewHolderBrowserList] */ - private void showUnverifiedNodeUi(List unverifiedNodes, MegaNode currentNode, ViewHolderBrowserList holder) { - for (int i = 0; i < unverifiedNodes.size(); i++) { - if (unverifiedNodes.get(i).getHandle() == currentNode.getHandle()) { - holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); - holder.permissionsIcon.setImageResource(R.drawable.serious_warning); - break; - } + private void showUnverifiedNodeUi(Set unverifiedNodes, MegaNode currentNode, ViewHolderBrowserList holder) { + if (unverifiedNodes.contains(currentNode.getHandle())) { + holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); + holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } } } From d9f106246d1cc289e337fcc60eb0b103be47c08a Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 4 Jan 2023 20:32:30 +0530 Subject: [PATCH 065/334] AND-15344 - Called openShareDialog from megaApi on click of share folder option. Minor mistake rectified in MegaApiFacade --- .../NodeOptionsBottomSheetDialogFragment.java | 36 ++++++++++++------- .../android/data/facade/MegaApiFacade.kt | 2 +- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 08b2d7d1dfd..57d5ab8926d 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -83,6 +83,7 @@ import mega.privacy.android.app.activities.WebViewActivity; import mega.privacy.android.app.imageviewer.ImageViewerActivity; import mega.privacy.android.app.interfaces.SnackbarShower; +import mega.privacy.android.app.listeners.OptionalMegaRequestListenerInterface; import mega.privacy.android.app.main.DrawerItem; import mega.privacy.android.app.main.FileContactListActivity; import mega.privacy.android.app.presentation.fileinfo.FileInfoActivity; @@ -98,7 +99,10 @@ import mega.privacy.android.app.utils.StringResourcesUtils; import mega.privacy.android.app.utils.ViewUtils; import mega.privacy.android.domain.entity.SortOrder; +import nz.mega.sdk.MegaApiJava; +import nz.mega.sdk.MegaError; import nz.mega.sdk.MegaNode; +import nz.mega.sdk.MegaRequest; import nz.mega.sdk.MegaShare; import nz.mega.sdk.MegaUser; import timber.log.Timber; @@ -954,19 +958,27 @@ public void onClick(View v) { break; case R.id.share_folder_option: - nodeType = checkBackupNodeTypeByHandle(megaApi, node); - if (isOutShare(node)) { - i = new Intent(requireContext(), FileContactListActivity.class); - i.putExtra(NAME, node.getHandle()); - startActivity(i); - } else { - if (nodeType != BACKUP_NONE) { - ((ManagerActivity) requireActivity()).showWarningDialogOfShare(node, nodeType, ACTION_BACKUP_SHARE_FOLDER); - } else { - nC.selectContactToShareFolder(node); + megaApi.openShareDialog(node, new OptionalMegaRequestListenerInterface() { + @Override + public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { + super.onRequestFinish(api, request, error); + if (error.getErrorCode() == MegaError.API_OK) { + int nodeType = checkBackupNodeTypeByHandle(megaApi, node); + if (isOutShare(node)) { + Intent intent = new Intent(requireContext(), FileContactListActivity.class); + intent.putExtra(NAME, node.getHandle()); + startActivity(intent); + } else { + if (nodeType != BACKUP_NONE) { + ((ManagerActivity) requireActivity()).showWarningDialogOfShare(node, nodeType, ACTION_BACKUP_SHARE_FOLDER); + } else { + nC.selectContactToShareFolder(node); + } + } + dismissAllowingStateLoss(); + } } - } - dismissAllowingStateLoss(); + }); break; case R.id.clear_share_option: diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index d25712811dd..bbec3d70c50 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -876,7 +876,7 @@ internal class MegaApiFacade @Inject constructor( megaApi.getUnverifiedIncomingShares(order) override suspend fun getUnverifiedOutgoingShares(order: Int): List = - megaApi.getUnverifiedIncomingShares(order) + megaApi.getUnverifiedOutgoingShares(order) override fun openShareDialog( megaNode: MegaNode, From 5d91c2ad3d2f2fbcdbb186bf884528d5cbed135b Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 4 Jan 2023 22:11:36 +0530 Subject: [PATCH 066/334] variable name of getUnverifiedInComingShares changed to getUnverifiedIncomingShares --- .../NodeOptionsBottomSheetDialogFragment.java | 171 ++++++++++-------- .../presentation/manager/ManagerViewModel.kt | 7 +- .../presentation/search/SearchViewModel.kt | 12 +- .../presentation/search/model/SearchState.kt | 2 + .../incoming/IncomingSharesViewModel.kt | 4 +- .../manager/ManagerViewModelTest.kt | 15 +- .../incoming/IncomingSharesViewModelTest.kt | 4 +- .../data/repository/MegaNodeRepository.kt | 2 +- 8 files changed, 120 insertions(+), 97 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 57d5ab8926d..114a1c936e9 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -67,6 +67,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.core.content.res.ResourcesCompat; +import androidx.lifecycle.Lifecycle; import androidx.lifecycle.ViewModelProvider; import com.google.android.material.switchmaterial.SwitchMaterial; @@ -77,10 +78,12 @@ import java.util.Collections; import java.util.List; +import kotlin.Unit; import mega.privacy.android.app.MegaOffline; import mega.privacy.android.app.MimeTypeList; import mega.privacy.android.app.R; import mega.privacy.android.app.activities.WebViewActivity; +import mega.privacy.android.app.arch.extensions.ViewExtensionsKt; import mega.privacy.android.app.imageviewer.ImageViewerActivity; import mega.privacy.android.app.interfaces.SnackbarShower; import mega.privacy.android.app.listeners.OptionalMegaRequestListenerInterface; @@ -328,80 +331,81 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } if (isOnline(requireContext())) { - if(searchViewModel.getMandatoryFingerPrintVerificationState().getValue()) { - ////TODO This flag for false for now. This will get manipulated after SDK changes - showOwnerSharedFolder(); - TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); - optionVerifyUser.setText(StringResourcesUtils.getString(R.string.shared_items_bottom_sheet_menu_verify_user, getMegaUserNameDB(user))); - nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); - optionVerifyUser.setVisibility(View.VISIBLE); - optionVerifyUser.setOnClickListener(this); - - //Removing the click listener & making it View.GONE - optionDownload.setOnClickListener(null); - optionDownload.setVisibility(View.GONE); - - //Removing the click listener & making it View.GONE - optionOffline.setOnClickListener(null); - optionOffline.setVisibility(View.GONE); - - separatorDownload.setVisibility(View.GONE); - separatorLabel.setVisibility(View.GONE); - separatorOpen.setVisibility(View.GONE); - separatorModify.setVisibility(View.GONE); - separatorShares.setVisibility(View.GONE); - - //Removing the click listener & making it View.GONE - optionSendChat.setOnClickListener(null); - optionSendChat.setVisibility(View.GONE); - - //Removing the click listener & making it View.GONE - optionCopy.setOnClickListener(null); - optionCopy.setVisibility(View.GONE); - - } else { - nodeName.setText(node.getName()); - if (node.isFolder()) { - optionVersionsLayout.setVisibility(View.GONE); - nodeInfo.setText(getMegaNodeFolderInfo(node)); - nodeVersionsIcon.setVisibility(View.GONE); + ViewExtensionsKt.collectFlow(requireActivity(), searchViewModel.getState(), Lifecycle.State.STARTED, state -> { + if (state.isMandatoryFingerPrintVerificationRequired()) { + showOwnerSharedFolder(); + TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); + optionVerifyUser.setText(StringResourcesUtils.getString(R.string.shared_items_bottom_sheet_menu_verify_user, getMegaUserNameDB(user))); + nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + optionVerifyUser.setVisibility(View.VISIBLE); + optionVerifyUser.setOnClickListener(this); + + //Removing the click listener & making it View.GONE + optionDownload.setOnClickListener(null); + optionDownload.setVisibility(View.GONE); - nodeThumb.setImageResource(getFolderIcon(node, drawerItem)); + //Removing the click listener & making it View.GONE + optionOffline.setOnClickListener(null); + optionOffline.setVisibility(View.GONE); - if (isEmptyFolder(node)) { - counterSave--; - optionOffline.setVisibility(View.GONE); - } + separatorDownload.setVisibility(View.GONE); + separatorLabel.setVisibility(View.GONE); + separatorOpen.setVisibility(View.GONE); + separatorModify.setVisibility(View.GONE); + separatorShares.setVisibility(View.GONE); - counterShares--; + //Removing the click listener & making it View.GONE + optionSendChat.setOnClickListener(null); optionSendChat.setVisibility(View.GONE); - } else { - if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize()) - && accessLevel >= MegaShare.ACCESS_READWRITE) { - optionEdit.setVisibility(View.VISIBLE); - } - - nodeInfo.setText(getFileInfo(node)); - if (megaApi.hasVersions(node)) { - nodeVersionsIcon.setVisibility(View.VISIBLE); - optionVersionsLayout.setVisibility(View.VISIBLE); - versions.setText(String.valueOf(megaApi.getNumVersions(node))); - } else { - nodeVersionsIcon.setVisibility(View.GONE); + //Removing the click listener & making it View.GONE + optionCopy.setOnClickListener(null); + optionCopy.setVisibility(View.GONE); + } else { + nodeName.setText(node.getName()); + if (node.isFolder()) { optionVersionsLayout.setVisibility(View.GONE); - } + nodeInfo.setText(getMegaNodeFolderInfo(node)); + nodeVersionsIcon.setVisibility(View.GONE); - setNodeThumbnail(requireContext(), node, nodeThumb); + nodeThumb.setImageResource(getFolderIcon(node, drawerItem)); + + if (isEmptyFolder(node)) { + counterSave--; + optionOffline.setVisibility(View.GONE); + } - if (isTakenDown) { counterShares--; optionSendChat.setVisibility(View.GONE); } else { - optionSendChat.setVisibility(View.VISIBLE); + if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize()) + && accessLevel >= MegaShare.ACCESS_READWRITE) { + optionEdit.setVisibility(View.VISIBLE); + } + + nodeInfo.setText(getFileInfo(node)); + + if (megaApi.hasVersions(node)) { + nodeVersionsIcon.setVisibility(View.VISIBLE); + optionVersionsLayout.setVisibility(View.VISIBLE); + versions.setText(String.valueOf(megaApi.getNumVersions(node))); + } else { + nodeVersionsIcon.setVisibility(View.GONE); + optionVersionsLayout.setVisibility(View.GONE); + } + + setNodeThumbnail(requireContext(), node, nodeThumb); + + if (isTakenDown) { + counterShares--; + optionSendChat.setVisibility(View.GONE); + } else { + optionSendChat.setVisibility(View.VISIBLE); + } } } - } + return Unit.INSTANCE; + }); } if (isTakenDown) { @@ -958,26 +962,21 @@ public void onClick(View v) { break; case R.id.share_folder_option: - megaApi.openShareDialog(node, new OptionalMegaRequestListenerInterface() { - @Override - public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { - super.onRequestFinish(api, request, error); - if (error.getErrorCode() == MegaError.API_OK) { - int nodeType = checkBackupNodeTypeByHandle(megaApi, node); - if (isOutShare(node)) { - Intent intent = new Intent(requireContext(), FileContactListActivity.class); - intent.putExtra(NAME, node.getHandle()); - startActivity(intent); - } else { - if (nodeType != BACKUP_NONE) { - ((ManagerActivity) requireActivity()).showWarningDialogOfShare(node, nodeType, ACTION_BACKUP_SHARE_FOLDER); - } else { - nC.selectContactToShareFolder(node); + ViewExtensionsKt.collectFlow(requireActivity(), searchViewModel.getState(), Lifecycle.State.STARTED, state -> { + if (state.isMandatoryFingerPrintVerificationRequired()) { + megaApi.openShareDialog(node, new OptionalMegaRequestListenerInterface() { + @Override + public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { + super.onRequestFinish(api, request, error); + if (error.getErrorCode() == MegaError.API_OK) { + showShareFolderOptions(); } } - dismissAllowingStateLoss(); - } + }); + } else { + showShareFolderOptions(); } + return Unit.INSTANCE; }); break; @@ -1205,4 +1204,20 @@ private int getAdapterType() { return INVALID_VALUE; } } + + private void showShareFolderOptions() { + int nodeType = checkBackupNodeTypeByHandle(megaApi, node); + if (isOutShare(node)) { + Intent intent = new Intent(requireContext(), FileContactListActivity.class); + intent.putExtra(NAME, node.getHandle()); + startActivity(intent); + } else { + if (nodeType != BACKUP_NONE) { + ((ManagerActivity) requireActivity()).showWarningDialogOfShare(node, nodeType, ACTION_BACKUP_SHARE_FOLDER); + } else { + nC.selectContactToShareFolder(node); + } + } + dismissAllowingStateLoss(); + } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index 6326ca2494b..c52914206af 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -24,6 +24,7 @@ import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates +import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.fragments.homepage.Event import mega.privacy.android.app.presentation.extensions.getState import mega.privacy.android.app.presentation.extensions.getStateFlow @@ -44,6 +45,7 @@ import mega.privacy.android.domain.usecase.BroadcastUploadPauseState import mega.privacy.android.domain.usecase.CheckCameraUpload import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetExtendedAccountDetail +import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetFullAccountInfo import mega.privacy.android.domain.usecase.GetNumUnreadUserAlerts import mega.privacy.android.domain.usecase.GetPricing @@ -107,7 +109,8 @@ class ManagerViewModel @Inject constructor( private val getPricing: GetPricing, private val getFullAccountInfo: GetFullAccountInfo, private val getActiveSubscription: GetActiveSubscription, - private val getUnverifiedInComingShares: GetUnverifiedIncomingShares, + private val getFeatureFlagValue: GetFeatureFlagValue, + private val getUnverifiedIncomingShares: GetUnverifiedIncomingShares, private val getUnverifiedOutgoingShares: GetUnverifiedOutgoingShares, ) : ViewModel() { @@ -165,7 +168,7 @@ class ManagerViewModel @Inject constructor( } viewModelScope.launch { - val incomingShares = getUnverifiedInComingShares(SortOrder.ORDER_DEFAULT_ASC).size + val incomingShares = getUnverifiedIncomingShares(SortOrder.ORDER_DEFAULT_ASC).size val outgoingShares = getUnverifiedOutgoingShares(SortOrder.ORDER_DEFAULT_ASC).size _state.update { it.copy(pendingActionsCount = incomingShares + outgoingShares) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt index f22ccdd347c..b8c7b5cea02 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt @@ -68,14 +68,6 @@ class SearchViewModel @Inject constructor( */ val stateLiveData = _state.map { Event(it) }.asLiveData() - private val _mandatoryFingerPrintVerificationState = MutableStateFlow(false) - - /** - * State for [MandatoryFingerPrintVerification] feature flag value - */ - val mandatoryFingerPrintVerificationState: StateFlow = - _mandatoryFingerPrintVerificationState - /** * Monitor global node updates */ @@ -344,8 +336,8 @@ class SearchViewModel @Inject constructor( */ fun isMandatoryFingerprintRequired() { viewModelScope.launch { - _mandatoryFingerPrintVerificationState.update { - getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification) + _state.update { + it.copy(isMandatoryFingerPrintVerificationRequired = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/model/SearchState.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/model/SearchState.kt index da0bf5bd659..153b6407b5e 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/model/SearchState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/model/SearchState.kt @@ -17,6 +17,7 @@ import nz.mega.sdk.MegaNode * @param searchQuery current search query * @param searchDepth current search depth count * @param isInProgress current progress state of the search request + * @param isMandatoryFingerPrintVerificationRequired - isMandatoryFingerPrintVerificationRequired */ data class SearchState( val nodes: List?, @@ -27,4 +28,5 @@ data class SearchState( val searchQuery: String?, val searchDepth: Int, val isInProgress: Boolean, + val isMandatoryFingerPrintVerificationRequired: Boolean = false, ) \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index bd4d2f27092..37d6e7b0e2b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -37,7 +37,7 @@ class IncomingSharesViewModel @Inject constructor( private val getOthersSortOrder: GetOthersSortOrder, monitorNodeUpdates: MonitorNodeUpdates, private val getFeatureFlagValue: GetFeatureFlagValue, - private val getUnverifiedInComingShares: GetUnverifiedIncomingShares, + private val getUnverifiedIncomingShares: GetUnverifiedIncomingShares, ) : ViewModel() { /** private UI state */ @@ -73,7 +73,7 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() - val unverifiedIncomingNodes = getUnverifiedInComingShares(_state.value.sortOrder) + val unverifiedIncomingNodes = getUnverifiedIncomingShares(_state.value.sortOrder) .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index f31c5bcc527..a7e4a9f90d2 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -21,6 +21,7 @@ import mega.privacy.android.app.domain.usecase.GetPrimarySyncHandle import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates +import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.manager.ManagerViewModel import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.manager.model.TransfersTab @@ -32,6 +33,7 @@ import mega.privacy.android.domain.entity.contacts.ContactRequestStatus import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.usecase.CheckCameraUpload import mega.privacy.android.domain.usecase.GetCloudSortOrder +import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetNumUnreadUserAlerts import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares @@ -72,11 +74,19 @@ class ManagerViewModelTest { private val checkCameraUpload = mock() private val getCloudSortOrder = mock() private val monitorConnectivity = mock() + + private val getFeatureFlagValue = + mock { + onBlocking { + invoke(AppFeatures.MandatoryFingerprintVerification) + }.thenReturn(true) + } + private val getUnverifiedOutgoingShares = mock { val shareData = ShareData("user", 8766L, 0, 987654678L, true) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } - private val getUnverifiedInComingShares = mock { + private val getUnverifiedIncomingShares = mock { val shareData = ShareData("user", 8766L, 0, 987654678L, true) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } @@ -118,7 +128,8 @@ class ManagerViewModelTest { getPricing = mock(), getFullAccountInfo = mock(), getActiveSubscription = mock(), - getUnverifiedInComingShares = getUnverifiedInComingShares, + getFeatureFlagValue = getFeatureFlagValue, + getUnverifiedIncomingShares = getUnverifiedIncomingShares, getUnverifiedOutgoingShares = getUnverifiedOutgoingShares, ) } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 07faa706a77..493fd0719e1 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -63,7 +63,7 @@ class IncomingSharesViewModelTest { }.thenReturn(true) } - private val getUnverifiedInComingShares = mock { + private val getUnverifiedIncomingShares = mock { val shareData = ShareData("user", 8766L, 0, 987654678L, true) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } @@ -84,7 +84,7 @@ class IncomingSharesViewModelTest { getOtherSortOrder, monitorNodeUpdates, getFeatureFlagValue, - getUnverifiedInComingShares, + getUnverifiedIncomingShares, ) } diff --git a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt index 93e10042a6d..06e25782ead 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt @@ -239,7 +239,7 @@ interface MegaNodeRepository { * * @return List of [ShareData] */ - suspend fun getUnVerifiedInComingShares(order: SortOrder): List + suspend fun getUnverifiedIncomingShares(order: SortOrder): List /** * Provides Unverified outgoing shares count from SDK From 4e17335df6f92adad6d23d6ac9ca117d1b3e6e00 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 5 Jan 2023 18:54:16 +0530 Subject: [PATCH 067/334] AND-15324 Security upgrade dialog placeholder icon replaced with expected icon --- .../SecurityUpgradeDialogView.kt | 2 +- .../main/res/drawable/ic_security_upgrade.xml | 102 ++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_security_upgrade.xml diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt index 06956ba3a26..4aebdfd3534 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt @@ -59,7 +59,7 @@ fun SecurityUpgradeDialogView( .height(140.dp) .width(114.dp) .testTag("HeaderImage"), - imageVector = ImageVector.vectorResource(id = R.drawable.ic_contact_verification_required), + imageVector = ImageVector.vectorResource(id = R.drawable.ic_security_upgrade), contentDescription = "Empty") Text(text = stringResource(id = R.string.shared_items_security_upgrade_dialog_title), diff --git a/app/src/main/res/drawable/ic_security_upgrade.xml b/app/src/main/res/drawable/ic_security_upgrade.xml new file mode 100644 index 00000000000..753190ec65e --- /dev/null +++ b/app/src/main/res/drawable/ic_security_upgrade.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 76e851e20d4e3d72dca3aac9261c348ed32b3af0 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 5 Jan 2023 19:09:15 +0530 Subject: [PATCH 068/334] Added content description --- .../presentation/fingerprintauth/SecurityUpgradeDialogView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt index 4aebdfd3534..96e9afe03cb 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt @@ -60,7 +60,7 @@ fun SecurityUpgradeDialogView( .width(114.dp) .testTag("HeaderImage"), imageVector = ImageVector.vectorResource(id = R.drawable.ic_security_upgrade), - contentDescription = "Empty") + contentDescription = "Security Upgrade Icon") Text(text = stringResource(id = R.string.shared_items_security_upgrade_dialog_title), style = subtitle1, From f7ce28478c76cec8290b0181645f5bfefe257d3a Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 5 Jan 2023 18:33:07 +0530 Subject: [PATCH 069/334] Security upgrade dialog displayed after receiving EVENT_UPGRADE_SECURITY --- .../android/app/main/ManagerActivity.java | 9 ++++++ .../presentation/manager/ManagerViewModel.kt | 30 ++++++++++++++++++- .../manager/model/ManagerState.kt | 10 ++++++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 6d83e2f353e..99987bc4c14 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -355,6 +355,7 @@ import mega.privacy.android.app.objects.PasscodeManagement; import mega.privacy.android.app.presentation.clouddrive.FileBrowserFragment; import mega.privacy.android.app.presentation.clouddrive.FileBrowserViewModel; +import mega.privacy.android.app.presentation.fingerprintauth.SecurityUpgradeDialogFragment; import mega.privacy.android.app.presentation.inbox.InboxFragment; import mega.privacy.android.app.presentation.inbox.InboxViewModel; import mega.privacy.android.app.presentation.manager.ManagerViewModel; @@ -451,6 +452,7 @@ import nz.mega.sdk.MegaChatRoom; import nz.mega.sdk.MegaContactRequest; import nz.mega.sdk.MegaError; +import nz.mega.sdk.MegaEvent; import nz.mega.sdk.MegaFolderInfo; import nz.mega.sdk.MegaNode; import nz.mega.sdk.MegaRequest; @@ -1424,6 +1426,13 @@ protected void onCreate(Bundle savedInstanceState) { return null; })); + ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, state -> { + if (viewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && state.getEventType() == MegaEvent.EVENT_UPGRADE_SECURITY) { + replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); + } + return Unit.INSTANCE; + }); + collectFlows(); viewModel.onGetNumUnreadUserAlerts().observe(this, this::updateNumUnreadUserAlerts); diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index c52914206af..a3aa78a8740 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -59,6 +59,7 @@ import mega.privacy.android.domain.usecase.MonitorStorageStateEvent import mega.privacy.android.domain.usecase.SendStatisticsMediaDiscovery import mega.privacy.android.domain.usecase.billing.GetActiveSubscription import mega.privacy.android.domain.usecase.viewtype.MonitorViewType +import nz.mega.sdk.MegaEvent import nz.mega.sdk.MegaNode import nz.mega.sdk.MegaUser import nz.mega.sdk.MegaUserAlert @@ -167,11 +168,31 @@ class ManagerViewModel @Inject constructor( } } + viewModelScope.launch { + _state.update { + it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) + } + } + viewModelScope.launch { val incomingShares = getUnverifiedIncomingShares(SortOrder.ORDER_DEFAULT_ASC).size + _state.update { + it.copy(pendingActionsCount = _state.value.pendingActionsCount + incomingShares) + } + } + + viewModelScope.launch { val outgoingShares = getUnverifiedOutgoingShares(SortOrder.ORDER_DEFAULT_ASC).size _state.update { - it.copy(pendingActionsCount = incomingShares + outgoingShares) + it.copy(pendingActionsCount = _state.value.pendingActionsCount + outgoingShares) + } + } + + viewModelScope.launch { + updateGlobalEvents.collect { megaEvent -> + _state.update { + it.copy(eventType = megaEvent.peekContent().type) + } } } } @@ -217,6 +238,13 @@ class ManagerViewModel @Inject constructor( .map { Event(it) } .asLiveData() + + private val updateGlobalEvents: Flow> = _updates + .filterIsInstance() + .mapNotNull { (event) -> + event?.let { Event(it) } + } + private fun checkItemForInbox(updatedNodes: List) { //Verify is it is a new item to the inbox inboxNode?.let { node -> diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index 19126a222a8..c565d8d4abc 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -1,8 +1,11 @@ package mega.privacy.android.app.presentation.manager.model +import nz.mega.sdk.MegaEvent + /** * Manager UI state * + * @param rubbishBinParentHandle current rubbish bin parent handle * @param isFirstNavigationLevel true if the navigation level is the first level * @param sharesTab current tab in shares screen * @param transfersTab current tab in transfers screen @@ -11,9 +14,12 @@ package mega.privacy.android.app.presentation.manager.model * @param shouldStopCameraUpload camera upload should be stopped or not * @param shouldSendCameraBroadcastEvent broadcast event should be sent or not * @param nodeUpdateReceived one-off event to notify UI that a node update occurred + * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param pendingActionsCount Pending actions count + * @param eventType [MegaEvent] type */ data class ManagerState( + val rubbishBinParentHandle: Long = -1L, val isFirstNavigationLevel: Boolean = true, val sharesTab: SharesTab = SharesTab.INCOMING_TAB, val transfersTab: TransfersTab = TransfersTab.NONE, @@ -22,5 +28,7 @@ data class ManagerState( val shouldStopCameraUpload: Boolean = false, val shouldSendCameraBroadcastEvent: Boolean = false, val nodeUpdateReceived: Boolean = false, - val pendingActionsCount: Int = 0 + val isMandatoryFingerprintVerificationNeeded: Boolean = false, + val pendingActionsCount: Int = 0, + val eventType: Int = -1, ) From 368ceafcc6fc8e2e5c4ecab6b948e15957abaedb Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 6 Jan 2023 13:02:28 +0530 Subject: [PATCH 070/334] dialog visibility check shifted to viewmodel. Unit test modified --- .../privacy/android/app/main/ManagerActivity.java | 2 +- .../app/presentation/manager/ManagerViewModel.kt | 10 ++++++---- .../app/presentation/manager/model/ManagerState.kt | 4 ++-- .../presentation/manager/ManagerViewModelTest.kt | 14 ++++++-------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 99987bc4c14..718ac3bb52b 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -1427,7 +1427,7 @@ protected void onCreate(Bundle savedInstanceState) { })); ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, state -> { - if (viewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && state.getEventType() == MegaEvent.EVENT_UPGRADE_SECURITY) { + if (viewModel.getState().getValue().getShowUpgradeSecurityAlert()) { replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); } return Unit.INSTANCE; diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index a3aa78a8740..d344aefc3b5 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -175,14 +175,14 @@ class ManagerViewModel @Inject constructor( } viewModelScope.launch { - val incomingShares = getUnverifiedIncomingShares(SortOrder.ORDER_DEFAULT_ASC).size + val incomingShares = getUnverifiedIncomingShares(getCloudSortOrder()).size _state.update { it.copy(pendingActionsCount = _state.value.pendingActionsCount + incomingShares) } } viewModelScope.launch { - val outgoingShares = getUnverifiedOutgoingShares(SortOrder.ORDER_DEFAULT_ASC).size + val outgoingShares = getUnverifiedOutgoingShares(getCloudSortOrder()).size _state.update { it.copy(pendingActionsCount = _state.value.pendingActionsCount + outgoingShares) } @@ -190,8 +190,10 @@ class ManagerViewModel @Inject constructor( viewModelScope.launch { updateGlobalEvents.collect { megaEvent -> - _state.update { - it.copy(eventType = megaEvent.peekContent().type) + if (_state.value.isMandatoryFingerprintVerificationNeeded && megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { + _state.update { + it.copy(showUpgradeSecurityAlert = true) + } } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index c565d8d4abc..0c39a300516 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -16,7 +16,7 @@ import nz.mega.sdk.MegaEvent * @param nodeUpdateReceived one-off event to notify UI that a node update occurred * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param pendingActionsCount Pending actions count - * @param eventType [MegaEvent] type + * @param showUpgradeSecurityAlert Boolean to decide whether to display security upgrade dialog or not */ data class ManagerState( val rubbishBinParentHandle: Long = -1L, @@ -30,5 +30,5 @@ data class ManagerState( val nodeUpdateReceived: Boolean = false, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val pendingActionsCount: Int = 0, - val eventType: Int = -1, + val showUpgradeSecurityAlert: Boolean = false, ) diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index a7e4a9f90d2..2e6ce45c653 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -82,14 +82,8 @@ class ManagerViewModelTest { }.thenReturn(true) } - private val getUnverifiedOutgoingShares = mock { - val shareData = ShareData("user", 8766L, 0, 987654678L, true) - onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) - } - private val getUnverifiedIncomingShares = mock { - val shareData = ShareData("user", 8766L, 0, 987654678L, true) - onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) - } + private val getUnverifiedOutgoingShares = mock() + private val getUnverifiedIncomingShares = mock() @get:Rule var instantExecutorRule = InstantTaskExecutorRule() @@ -421,6 +415,10 @@ class ManagerViewModelTest { @Test fun `test that pending actions count is not null`() = runTest { + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + val shareData1 = ShareData("user", 8766L, 0, 987654678L, true) + whenever(getUnverifiedIncomingShares(getCloudSortOrder())).thenReturn(listOf(shareData, + shareData1)) setUnderTest() underTest.state.map { it.pendingActionsCount }.distinctUntilChanged().test { assertThat(awaitItem()).isEqualTo(2) From 0158b8b0c32ad9742314e6a82a49d5056435debf Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 6 Jan 2023 13:16:33 +0530 Subject: [PATCH 071/334] unused import removed --- .../android/app/presentation/manager/model/ManagerState.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index 0c39a300516..706eab01286 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -1,7 +1,5 @@ package mega.privacy.android.app.presentation.manager.model -import nz.mega.sdk.MegaEvent - /** * Manager UI state * From f9b89a52c3a699213ae8ed60e31a774c40ec0c95 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 6 Jan 2023 13:20:21 +0530 Subject: [PATCH 072/334] Unused imports removed --- .../main/java/mega/privacy/android/app/main/ManagerActivity.java | 1 - .../privacy/android/app/presentation/manager/ManagerViewModel.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 718ac3bb52b..c47b7feeb1d 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -452,7 +452,6 @@ import nz.mega.sdk.MegaChatRoom; import nz.mega.sdk.MegaContactRequest; import nz.mega.sdk.MegaError; -import nz.mega.sdk.MegaEvent; import nz.mega.sdk.MegaFolderInfo; import nz.mega.sdk.MegaNode; import nz.mega.sdk.MegaRequest; diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index d344aefc3b5..7516f66e0c1 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -34,7 +34,6 @@ import mega.privacy.android.app.presentation.manager.model.TransfersTab import mega.privacy.android.app.utils.livedata.SingleLiveEvent import mega.privacy.android.data.model.GlobalUpdate import mega.privacy.android.domain.entity.Product -import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.StorageState import mega.privacy.android.domain.entity.billing.MegaPurchase import mega.privacy.android.domain.entity.contacts.ContactRequest From 18eb198cbf984f82e915730af5cc998f4bf8564c Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 6 Jan 2023 13:33:47 +0530 Subject: [PATCH 073/334] Modified Ui state variable name to a more meaningful name --- .../java/mega/privacy/android/app/main/ManagerActivity.java | 2 +- .../android/app/presentation/manager/ManagerViewModel.kt | 2 +- .../android/app/presentation/manager/model/ManagerState.kt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index c47b7feeb1d..fa340d29f37 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -1426,7 +1426,7 @@ protected void onCreate(Bundle savedInstanceState) { })); ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, state -> { - if (viewModel.getState().getValue().getShowUpgradeSecurityAlert()) { + if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); } return Unit.INSTANCE; diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index 7516f66e0c1..da11349e521 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -191,7 +191,7 @@ class ManagerViewModel @Inject constructor( updateGlobalEvents.collect { megaEvent -> if (_state.value.isMandatoryFingerprintVerificationNeeded && megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { _state.update { - it.copy(showUpgradeSecurityAlert = true) + it.copy(shouldAlertUserAboutSecurityUpgrade = true) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index 706eab01286..8dca9970c14 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -14,7 +14,7 @@ package mega.privacy.android.app.presentation.manager.model * @param nodeUpdateReceived one-off event to notify UI that a node update occurred * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param pendingActionsCount Pending actions count - * @param showUpgradeSecurityAlert Boolean to decide whether to display security upgrade dialog or not + * @param shouldAlertUserAboutSecurityUpgrade Boolean to decide whether to display security upgrade dialog or not */ data class ManagerState( val rubbishBinParentHandle: Long = -1L, @@ -28,5 +28,5 @@ data class ManagerState( val nodeUpdateReceived: Boolean = false, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val pendingActionsCount: Int = 0, - val showUpgradeSecurityAlert: Boolean = false, + val shouldAlertUserAboutSecurityUpgrade: Boolean = false, ) From c25cf5147320cb56faf4e80568edbe2b200ea445 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 9 Jan 2023 20:14:45 +0530 Subject: [PATCH 074/334] AND-15366 Added setSecureFlag in MegaApiGateway & added implementation in MegaApiFacade --- .../java/mega/privacy/android/data/facade/MegaApiFacade.kt | 3 +++ .../privacy/android/data/gateway/api/MegaApiGateway.kt | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index bbec3d70c50..61c237d0b3d 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -886,4 +886,7 @@ internal class MegaApiFacade @Inject constructor( override fun upgradeSecurity(listener: MegaRequestListenerInterface) = megaApi.upgradeSecurity(listener) + override fun setSecureFlag(enable: Boolean) { + megaApi.setSecureFlag(enable) + } } diff --git a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt index bfc8144b431..7351a0fe846 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt @@ -1709,4 +1709,11 @@ interface MegaApiGateway { * @param listener : Listener to track this request */ fun upgradeSecurity(listener: MegaRequestListenerInterface) + + /** + * Sets the secure flag to true or false while sharing a node + * + * @param enable : Boolean value + */ + fun setSecureFlag(enable: Boolean) } From a083f2ba9be8d862810d6a9b394f413a495944c6 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 9 Jan 2023 22:15:18 +0530 Subject: [PATCH 075/334] Code formatted --- .../java/mega/privacy/android/data/facade/MegaApiFacade.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index 61c237d0b3d..eee37d0e466 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -886,7 +886,6 @@ internal class MegaApiFacade @Inject constructor( override fun upgradeSecurity(listener: MegaRequestListenerInterface) = megaApi.upgradeSecurity(listener) - override fun setSecureFlag(enable: Boolean) { - megaApi.setSecureFlag(enable) - } + override fun setSecureFlag(enable: Boolean) = megaApi.setSecureFlag(enable) + } From 6f6ba121ac40e66303a6b8b0551066bc50309cb3 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 10 Jan 2023 09:44:37 +0530 Subject: [PATCH 076/334] Added a deprecated warning as the API is for testing purpose --- .../main/java/mega/privacy/android/data/facade/MegaApiFacade.kt | 1 + .../java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index eee37d0e466..0319080ef80 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -886,6 +886,7 @@ internal class MegaApiFacade @Inject constructor( override fun upgradeSecurity(listener: MegaRequestListenerInterface) = megaApi.upgradeSecurity(listener) + @Deprecated("This API is for testing purpose, will be deleted later") override fun setSecureFlag(enable: Boolean) = megaApi.setSecureFlag(enable) } diff --git a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt index 7351a0fe846..17ef0022c36 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt @@ -1715,5 +1715,6 @@ interface MegaApiGateway { * * @param enable : Boolean value */ + @Deprecated("This API is for testing purpose, will be deleted later") fun setSecureFlag(enable: Boolean) } From 46814135f157e3fda407e7be34b64f7579d0c2fd Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 10 Jan 2023 12:06:38 +0530 Subject: [PATCH 077/334] Added setSecureFlag use case --- .../mega/privacy/android/app/di/GetNodeModule.kt | 11 +++++++++++ .../android/domain/usecase/SetSecureFlag.kt | 14 ++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 domain/src/main/kotlin/mega/privacy/android/domain/usecase/SetSecureFlag.kt diff --git a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt index 040be52788f..6736fcc4c15 100644 --- a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt @@ -15,6 +15,7 @@ import mega.privacy.android.app.usecase.MoveNodeUseCase import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares +import mega.privacy.android.domain.usecase.SetSecureFlag import mega.privacy.android.domain.usecase.filenode.CopyNodeByHandle import mega.privacy.android.domain.usecase.filenode.CopyNodeByHandleChangingName import mega.privacy.android.domain.usecase.filenode.MoveNodeByHandle @@ -128,5 +129,15 @@ abstract class GetNodeModule { MoveNodeByHandle { nodeToCopy, newNodeParent -> moveNodeUseCase.move(nodeToCopy.longValue, newNodeParent.longValue).await() } + + /** + * Provides [SetSecureFlag] implementation + * + * @param megaNodeRepository [MegaNodeRepository] + * @return [SetSecureFlag] + */ + @Provides + fun provideSetSecureFlag(megaNodeRepository: MegaNodeRepository): SetSecureFlag = + SetSecureFlag(megaNodeRepository::setSecureFlag) } } diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/SetSecureFlag.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/SetSecureFlag.kt new file mode 100644 index 00000000000..425bf3a1a0b --- /dev/null +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/SetSecureFlag.kt @@ -0,0 +1,14 @@ +package mega.privacy.android.domain.usecase + +/** + * Interface to set secure flag value to true or false + */ +fun interface SetSecureFlag { + + /** + * Invoke + * + * @param enable : Boolean value + */ + suspend operator fun invoke(enable: Boolean) +} \ No newline at end of file From f980dac787dcc92c61ebccfb63663859f33b5518 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 12 Jan 2023 14:53:44 +0530 Subject: [PATCH 078/334] AND-15405 UI bugs for mandatory fingerprint verification are fixed --- .../assets/featuretoggle/feature_flags.json | 4 + .../mega/privacy/android/app/BaseActivity.kt | 2 +- .../android/app/main/ManagerActivity.java | 43 ++-- .../app/main/adapters/MegaNodeAdapter.java | 7 +- .../NodeOptionsBottomSheetDialogFragment.java | 187 ++++++++++-------- .../shares/incoming/IncomingSharesFragment.kt | 1 + .../incoming/IncomingSharesViewModel.kt | 11 +- .../incoming/model/IncomingSharesState.kt | 4 +- .../shares/outgoing/OutgoingSharesFragment.kt | 1 + .../outgoing/OutgoingSharesViewModel.kt | 11 +- .../outgoing/model/OutgoingSharesState.kt | 2 + .../layout/bottom_pending_actions_badge.xml | 29 +++ 12 files changed, 191 insertions(+), 111 deletions(-) create mode 100644 app/src/main/res/layout/bottom_pending_actions_badge.xml diff --git a/app/src/debug/assets/featuretoggle/feature_flags.json b/app/src/debug/assets/featuretoggle/feature_flags.json index dd9c6fb6fe3..676a4c1439e 100644 --- a/app/src/debug/assets/featuretoggle/feature_flags.json +++ b/app/src/debug/assets/featuretoggle/feature_flags.json @@ -2,5 +2,9 @@ { "name": "PermanentLogging", "value": true + }, + { + "name": "MandatoryFingerprintVerification", + "value": true } ] \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt index a1823be9708..b01ff13255c 100644 --- a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt @@ -463,7 +463,7 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - + megaApi.setSecureFlag(true) nameCollisionActivityContract = registerForActivityResult(NameCollisionActivityContract()) { result: String? -> if (result != null) { diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index fa340d29f37..588ad7e78eb 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -847,8 +847,6 @@ private enum HomepageScreen { int bottomNavigationCurrentItem = -1; View chatBadge; View callBadge; - View pendingActionsBadge; - BottomNavigationItemView sharedItemsView; private boolean joiningToChatLink; private String linkJoinToChatLink; @@ -1811,11 +1809,6 @@ public boolean onPreDraw() { itemView.addView(chatBadge); setChatBadge(); - // Navi button Shared Items - sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); - pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_chat_badge, menuView, false); - setPendingActionsBadge(); - callBadge = LayoutInflater.from(this).inflate(R.layout.bottom_call_badge, menuView, false); itemView.addView(callBadge); callBadge.setVisibility(View.GONE); @@ -2607,20 +2600,20 @@ public void onPageScrollStateChanged(int state) { } ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { - addUnverifiedIncomingCountBadge(incomingSharesState.getUnVerifiedIncomingNodes().size()); + addUnverifiedIncomingCountBadge(incomingSharesState.getUnverifiedIncomingShares().size()); } return Unit.INSTANCE; }); ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { - addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnVerifiedOutgoingNodes().size()); + addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnverifiedOutgoingShares().size()); } return Unit.INSTANCE; }); - } - + setPendingActionsBadge(menuView); + } /** * collecting Flows from ViewModels @@ -10288,6 +10281,7 @@ public AndroidCompletedTransfer getSelectedTransfer() { } public MegaNode getSelectedNode() { + callOpenShareDialog(); return selectedNode; } @@ -10520,17 +10514,30 @@ public void setChatBadge() { } } - public void setPendingActionsBadge() { - if (viewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded()) { - sharedItemsView.addView(pendingActionsBadge); - ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { - TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.chat_badge_text); - tvPendingActionsCount.setText(managerState.getPendingActionsCount()); - return Unit.INSTANCE; + private void callOpenShareDialog() { + if (searchViewModel.getState().getValue().isMandatoryFingerPrintVerificationRequired()) { + megaApi.openShareDialog(selectedNode, new OptionalMegaRequestListenerInterface() { + @Override + public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { + super.onRequestFinish(api, request, error); + } }); } } + public void setPendingActionsBadge(BottomNavigationMenuView menuView) { + ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { + if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { + BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); + View pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_pending_actions_badge, menuView, false); + sharedItemsView.addView(pendingActionsBadge); + TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.pending_actions_badge_text); + tvPendingActionsCount.setText(String.valueOf(managerState.getPendingActionsCount())); + } + return Unit.INSTANCE; + }); + } + private void setCallBadge() { if (!viewModel.isConnected() || megaChatApi.getNumCalls() <= 0 || (megaChatApi.getNumCalls() == 1 && participatingInACall())) { callBadge.setVisibility(View.GONE); diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index bbddf9a4917..481cc412967 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -68,6 +68,7 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -144,9 +145,9 @@ public class MegaNodeAdapter extends RecyclerView.Adapter unverifiedIncomingNodeHandles; - private Set unverifiedOutgoingNodeHandles; - private Boolean isMandatoryFingerprintVerificationNeeded; + private Set unverifiedIncomingNodeHandles = new HashSet<>(); + private Set unverifiedOutgoingNodeHandles = new HashSet<>(); + private boolean isMandatoryFingerprintVerificationNeeded; public static class ViewHolderBrowser extends RecyclerView.ViewHolder { diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 114a1c936e9..be190728956 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -96,11 +96,14 @@ import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity; import mega.privacy.android.app.presentation.manager.model.SharesTab; import mega.privacy.android.app.presentation.search.SearchViewModel; +import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel; +import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel; import mega.privacy.android.app.utils.AlertDialogUtil; import mega.privacy.android.app.utils.Constants; import mega.privacy.android.app.utils.MegaNodeUtil; import mega.privacy.android.app.utils.StringResourcesUtils; import mega.privacy.android.app.utils.ViewUtils; +import mega.privacy.android.domain.entity.ShareData; import mega.privacy.android.domain.entity.SortOrder; import nz.mega.sdk.MegaApiJava; import nz.mega.sdk.MegaError; @@ -174,6 +177,9 @@ public class NodeOptionsBottomSheetDialogFragment extends BaseBottomSheetDialogF private MegaUser user; + private IncomingSharesViewModel incomingSharesViewModel; + private OutgoingSharesViewModel outgoingSharesViewModel; + public NodeOptionsBottomSheetDialogFragment(int mode) { if (mode >= DEFAULT_MODE && mode <= FAVOURITES_MODE) { mMode = mode; @@ -184,14 +190,19 @@ public NodeOptionsBottomSheetDialogFragment() { mMode = DEFAULT_MODE; } + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class); + incomingSharesViewModel = new ViewModelProvider(requireActivity()).get(IncomingSharesViewModel.class); + outgoingSharesViewModel = new ViewModelProvider(requireActivity()).get(OutgoingSharesViewModel.class); + } + @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { contentView = View.inflate(getContext(), R.layout.bottom_sheet_node_item, null); itemsLayout = contentView.findViewById(R.id.items_layout_bottom_sheet_node); - - searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class); - if (savedInstanceState != null) { long handle = savedInstanceState.getLong(HANDLE, INVALID_HANDLE); node = megaApi.getNodeByHandle(handle); @@ -224,6 +235,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat nodeInfo = contentView.findViewById(R.id.node_info_text); ImageView nodeVersionsIcon = contentView.findViewById(R.id.node_info_versions_icon); + LinearLayout optionOffline = contentView.findViewById(R.id.option_offline_layout); + ImageView permissionsIcon = contentView.findViewById(R.id.permissions_icon); LinearLayout optionEdit = contentView.findViewById(R.id.edit_file_option); @@ -239,7 +252,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat TextView optionLabelCurrent = contentView.findViewById(R.id.option_label_current); // counterSave TextView optionDownload = contentView.findViewById(R.id.download_option); - LinearLayout optionOffline = contentView.findViewById(R.id.option_offline_layout); + SwitchMaterial offlineSwitch = contentView.findViewById(R.id.file_properties_switch); // counterShares TextView optionLink = contentView.findViewById(R.id.link_option); @@ -331,81 +344,47 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } if (isOnline(requireContext())) { - ViewExtensionsKt.collectFlow(requireActivity(), searchViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (state.isMandatoryFingerPrintVerificationRequired()) { - showOwnerSharedFolder(); - TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); - optionVerifyUser.setText(StringResourcesUtils.getString(R.string.shared_items_bottom_sheet_menu_verify_user, getMegaUserNameDB(user))); - nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); - optionVerifyUser.setVisibility(View.VISIBLE); - optionVerifyUser.setOnClickListener(this); - - //Removing the click listener & making it View.GONE - optionDownload.setOnClickListener(null); - optionDownload.setVisibility(View.GONE); + nodeName.setText(node.getName()); + if (node.isFolder()) { + optionVersionsLayout.setVisibility(View.GONE); + nodeInfo.setText(getMegaNodeFolderInfo(node)); + nodeVersionsIcon.setVisibility(View.GONE); + + nodeThumb.setImageResource(getFolderIcon(node, drawerItem)); - //Removing the click listener & making it View.GONE - optionOffline.setOnClickListener(null); + if (isEmptyFolder(node)) { + counterSave--; optionOffline.setVisibility(View.GONE); + } - separatorDownload.setVisibility(View.GONE); - separatorLabel.setVisibility(View.GONE); - separatorOpen.setVisibility(View.GONE); - separatorModify.setVisibility(View.GONE); - separatorShares.setVisibility(View.GONE); + counterShares--; + optionSendChat.setVisibility(View.GONE); + } else { + if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize()) + && accessLevel >= MegaShare.ACCESS_READWRITE) { + optionEdit.setVisibility(View.VISIBLE); + } - //Removing the click listener & making it View.GONE - optionSendChat.setOnClickListener(null); - optionSendChat.setVisibility(View.GONE); + nodeInfo.setText(getFileInfo(node)); - //Removing the click listener & making it View.GONE - optionCopy.setOnClickListener(null); - optionCopy.setVisibility(View.GONE); + if (megaApi.hasVersions(node)) { + nodeVersionsIcon.setVisibility(View.VISIBLE); + optionVersionsLayout.setVisibility(View.VISIBLE); + versions.setText(String.valueOf(megaApi.getNumVersions(node))); } else { - nodeName.setText(node.getName()); - if (node.isFolder()) { - optionVersionsLayout.setVisibility(View.GONE); - nodeInfo.setText(getMegaNodeFolderInfo(node)); - nodeVersionsIcon.setVisibility(View.GONE); - - nodeThumb.setImageResource(getFolderIcon(node, drawerItem)); - - if (isEmptyFolder(node)) { - counterSave--; - optionOffline.setVisibility(View.GONE); - } - - counterShares--; - optionSendChat.setVisibility(View.GONE); - } else { - if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize()) - && accessLevel >= MegaShare.ACCESS_READWRITE) { - optionEdit.setVisibility(View.VISIBLE); - } - - nodeInfo.setText(getFileInfo(node)); - - if (megaApi.hasVersions(node)) { - nodeVersionsIcon.setVisibility(View.VISIBLE); - optionVersionsLayout.setVisibility(View.VISIBLE); - versions.setText(String.valueOf(megaApi.getNumVersions(node))); - } else { - nodeVersionsIcon.setVisibility(View.GONE); - optionVersionsLayout.setVisibility(View.GONE); - } + nodeVersionsIcon.setVisibility(View.GONE); + optionVersionsLayout.setVisibility(View.GONE); + } - setNodeThumbnail(requireContext(), node, nodeThumb); + setNodeThumbnail(requireContext(), node, nodeThumb); - if (isTakenDown) { - counterShares--; - optionSendChat.setVisibility(View.GONE); - } else { - optionSendChat.setVisibility(View.VISIBLE); - } - } + if (isTakenDown) { + counterShares--; + optionSendChat.setVisibility(View.GONE); + } else { + optionSendChat.setVisibility(View.VISIBLE); } - return Unit.INSTANCE; - }); + } } if (isTakenDown) { @@ -742,6 +721,23 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } super.onViewCreated(view, savedInstanceState); + if(nC.nodeComesFromIncoming(node)) { + ViewExtensionsKt.collectFlow(requireActivity(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { + if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE) { + setUnverifiedNodeUserName(incomingSharesViewModel.getState().getValue().getUnverifiedIncomingShares()); + hideNodeActions(); + } + return Unit.INSTANCE; + }); + } else { + ViewExtensionsKt.collectFlow(requireActivity(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { + if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE) { + setUnverifiedNodeUserName(outgoingSharesViewModel.getState().getValue().getUnverifiedOutgoingShares()); + hideNodeActions(); + } + return Unit.INSTANCE; + }); + } } @Override @@ -882,6 +878,40 @@ private void showOwnerSharedFolder() { } } } + private void setUnverifiedNodeUserName(List shareDataList) { + for (int j = 0; j < shareDataList.size(); j++) { + ShareData mS = shareDataList.get(j); + if (mS.getNodeHandle() == node.getHandle()) { + user = megaApi.getContact(mS.getUser()); + if (user != null) { + nodeInfo.setText(getMegaUserNameDB(user)); + } else { + nodeInfo.setText(mS.getUser()); + } + } + } + } + + private void hideNodeActions() { + TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); + optionVerifyUser.setText(StringResourcesUtils.getString(R.string.shared_items_bottom_sheet_menu_verify_user, getMegaUserNameDB(user))); + TextView nodeName = contentView.findViewById(R.id.node_name_text); + nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + optionVerifyUser.setVisibility(View.VISIBLE); + optionVerifyUser.setOnClickListener(this); + + contentView.findViewById(R.id.favorite_option).setVisibility(View.GONE); + contentView.findViewById(R.id.rename_option).setVisibility(View.GONE); + contentView.findViewById(R.id.link_option).setVisibility(View.GONE); + contentView.findViewById(R.id.remove_option).setVisibility(View.GONE); + contentView.findViewById(R.id.download_option).setVisibility(View.GONE); + contentView.findViewById(R.id.option_offline_layout).setVisibility(View.GONE); + contentView.findViewById(R.id.copy_option).setVisibility(View.GONE); + contentView.findViewById(R.id.rubbish_bin_option).setVisibility(View.GONE); + contentView.findViewById(R.id.share_option).setVisibility(View.GONE); + contentView.findViewById(R.id.share_folder_option).setVisibility(View.GONE); + contentView.findViewById(R.id.clear_share_option).setVisibility(View.GONE); + } @SuppressLint("NonConstantResourceId") @Override @@ -962,22 +992,7 @@ public void onClick(View v) { break; case R.id.share_folder_option: - ViewExtensionsKt.collectFlow(requireActivity(), searchViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (state.isMandatoryFingerPrintVerificationRequired()) { - megaApi.openShareDialog(node, new OptionalMegaRequestListenerInterface() { - @Override - public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { - super.onRequestFinish(api, request, error); - if (error.getErrorCode() == MegaError.API_OK) { - showShareFolderOptions(); - } - } - }); - } else { - showShareFolderOptions(); - } - return Unit.INSTANCE; - }); + showShareFolderOptions(); break; case R.id.clear_share_option: diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 9d62608e24c..1ab2317f119 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -241,6 +241,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) adapter?.setUnverifiedIncomingNodes(it.unVerifiedIncomingNodes) + adapter?.notifyDataSetChanged() updateNodes(it.unVerifiedIncomingNodes + it.nodes) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 37d6e7b0e2b..7bcc95ae45b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -73,7 +73,16 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() - val unverifiedIncomingNodes = getUnverifiedIncomingShares(_state.value.sortOrder) + } + + viewModelScope.launch { + _state.update { + it.copy(unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder)) + } + } + + viewModelScope.launch { + val unverifiedIncomingNodes = _state.value.unverifiedIncomingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index 3baf88523a9..e758b69c7b2 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -15,7 +15,8 @@ import nz.mega.sdk.MegaNode * @param isLoading true if the nodes are loading * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed - * @param unVerifiedIncomingNodes List of unverified Incoming [MegaNode] + * @param unverifiedIncomingShares List of unverified incoming [ShareData] + * @param unVerifiedIncomingNodes List of unverified incoming [MegaNode] */ data class IncomingSharesState( val incomingHandle: Long = -1L, @@ -26,6 +27,7 @@ data class IncomingSharesState( val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, + val unverifiedIncomingShares: List = emptyList(), val unVerifiedIncomingNodes: List = emptyList(), ) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 3ab104c61fc..b377a5d908d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -235,6 +235,7 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) adapter?.setUnverifiedOutgoingNodes(it.unVerifiedOutgoingNodes) + adapter?.notifyDataSetChanged() updateNodes(it.unVerifiedOutgoingNodes + it.nodes) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index bc76d911b88..3141de62510 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -58,7 +58,16 @@ class OutgoingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() - val unverifiedOutgoingNodes = getUnverifiedOutgoingShares(_state.value.sortOrder) + } + + viewModelScope.launch { + _state.update { + it.copy(unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder)) + } + } + + viewModelScope.launch { + val unverifiedOutgoingNodes = _state.value.unverifiedOutgoingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index b9b121326e1..66c7394278e 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -15,6 +15,7 @@ import nz.mega.sdk.MegaNode * @param isLoading true if the nodes are loading * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed + * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodes List of Unverified outgoing [MegaNode] */ data class OutgoingSharesState( @@ -26,6 +27,7 @@ data class OutgoingSharesState( val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, + val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodes: List = emptyList(), ) { diff --git a/app/src/main/res/layout/bottom_pending_actions_badge.xml b/app/src/main/res/layout/bottom_pending_actions_badge.xml new file mode 100644 index 00000000000..a5cf0a8a719 --- /dev/null +++ b/app/src/main/res/layout/bottom_pending_actions_badge.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file From 01348e48b2da598872f88944b624d3d0f65315d9 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 12 Jan 2023 15:12:43 +0530 Subject: [PATCH 079/334] text color changed to black --- .../authenticitycredendials/view/AuthenticityCredentialsView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt index 28f0aaf2a23..3c648f93bdd 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt @@ -152,7 +152,7 @@ fun ContactCredentials( bottom = 14.dp, end = 48.dp), style = MaterialTheme.typography.body2, - color = if (MaterialTheme.colors.isLight) black else white_alpha_087, + color = black, text = stringResource(id = R.string.shared_items_verify_credentials_verify_person_banner_label)) IconButton( From 26289e251c10f378228851a1550e0d0f52f619c4 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 12 Jan 2023 20:30:39 +0530 Subject: [PATCH 080/334] AND - 15405 Unverified node UI modified to match design --- .../app/main/adapters/MegaNodeAdapter.java | 25 +++++++++++-------- .../shares/incoming/IncomingSharesFragment.kt | 5 ++-- .../shares/outgoing/OutgoingSharesFragment.kt | 5 ++-- .../outgoing/OutgoingSharesViewModel.kt | 9 ------- .../outgoing/model/OutgoingSharesState.kt | 1 - .../outgoing/OutgoingSharesViewModelTest.kt | 2 +- 6 files changed, 19 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 481cc412967..9b1a4a35456 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -102,6 +102,7 @@ import mega.privacy.android.app.utils.ThumbnailUtils; import mega.privacy.android.data.database.DatabaseHandler; import mega.privacy.android.data.model.MegaContactDB; +import mega.privacy.android.domain.entity.ShareData; import mega.privacy.android.domain.entity.SortOrder; import nz.mega.sdk.MegaApiAndroid; import nz.mega.sdk.MegaNode; @@ -1523,32 +1524,34 @@ public void setMandatoryFingerprintVerificationValue(boolean isVerificationNeede /** * Adds unverified incoming nodes to Set * - * @param nodes - List of incoming [MegaNode] + * @param shares - List of incoming [ShareData] */ - public void setUnverifiedIncomingNodes(List nodes) { + public void setUnverifiedIncomingNodes(List shares) { unverifiedIncomingNodeHandles.clear(); - nodes.forEach(megaNode -> unverifiedIncomingNodeHandles.add(megaNode.getHandle())); + shares.forEach(share -> unverifiedIncomingNodeHandles.add(share.getNodeHandle())); } /** * Adds unverified outgoing nodes to Set * - * @param nodes - List of outgoing [MegaNode] + * @param shares - List of outgoing [ShareData] */ - public void setUnverifiedOutgoingNodes(List nodes) { + public void setUnverifiedOutgoingNodes(List shares) { unverifiedOutgoingNodeHandles.clear(); - nodes.forEach(megaNode -> unverifiedOutgoingNodeHandles.add(megaNode.getHandle())); + shares.forEach(share -> unverifiedOutgoingNodeHandles.add(share.getNodeHandle())); } /** * Function to check if current node is unverified & show Ui items accordingly * - * @param unverifiedNodes Unverified Nodes List - * @param currentNode Current node from adapter - * @param holder [ViewHolderBrowserList] + * @param unverifiedNodeHandles Unverified Node handles list + * @param currentNode Current node from adapter + * @param holder [ViewHolderBrowserList] */ - private void showUnverifiedNodeUi(Set unverifiedNodes, MegaNode currentNode, ViewHolderBrowserList holder) { - if (unverifiedNodes.contains(currentNode.getHandle())) { + private void showUnverifiedNodeUi(Set unverifiedNodeHandles, MegaNode currentNode, ViewHolderBrowserList holder) { + if (unverifiedNodeHandles.contains(currentNode.getHandle())) { + holder.permissionsIcon.setVisibility(View.VISIBLE); + holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 1ab2317f119..84535cb35ec 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -240,9 +240,8 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { hideActionMode() setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) - adapter?.setUnverifiedIncomingNodes(it.unVerifiedIncomingNodes) - adapter?.notifyDataSetChanged() - updateNodes(it.unVerifiedIncomingNodes + it.nodes) + adapter?.setUnverifiedIncomingNodes(it.unverifiedIncomingShares) + updateNodes(it.nodes) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index b377a5d908d..cb1639c360d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -234,9 +234,8 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { hideActionMode() setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) - adapter?.setUnverifiedOutgoingNodes(it.unVerifiedOutgoingNodes) - adapter?.notifyDataSetChanged() - updateNodes(it.unVerifiedOutgoingNodes + it.nodes) + adapter?.setUnverifiedOutgoingNodes(it.unverifiedOutgoingShares) + updateNodes(it.nodes) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 3141de62510..365bcebd357 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -65,15 +65,6 @@ class OutgoingSharesViewModel @Inject constructor( it.copy(unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder)) } } - - viewModelScope.launch { - val unverifiedOutgoingNodes = _state.value.unverifiedOutgoingShares - .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } - _state.update { - it.copy(unVerifiedOutgoingNodes = unverifiedOutgoingNodes) - } - } } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index 66c7394278e..ead168c2c42 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -28,7 +28,6 @@ data class OutgoingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), - val unVerifiedOutgoingNodes: List = emptyList(), ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index f7bfb4d5a09..a97d38b03e3 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -450,7 +450,7 @@ class OutgoingSharesViewModelTest { whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() initViewModel() - underTest.state.map { it.unVerifiedOutgoingNodes }.distinctUntilChanged() + underTest.state.map { it.unverifiedOutgoingShares }.distinctUntilChanged() .test { assertThat(awaitItem().size).isEqualTo(1) } From 8870bf666643fbecda121f0ffa02dbdb0f7af8ea Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 12 Jan 2023 22:12:07 +0530 Subject: [PATCH 081/334] AND-15405 Unverified check added to Bottom sheet dialog actions --- .../NodeOptionsBottomSheetDialogFragment.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index be190728956..e49933a16a3 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -76,7 +76,9 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import kotlin.Unit; import mega.privacy.android.app.MegaOffline; @@ -179,6 +181,7 @@ public class NodeOptionsBottomSheetDialogFragment extends BaseBottomSheetDialogF private IncomingSharesViewModel incomingSharesViewModel; private OutgoingSharesViewModel outgoingSharesViewModel; + private Set unverifiedHandles = new HashSet<>(); public NodeOptionsBottomSheetDialogFragment(int mode) { if (mode >= DEFAULT_MODE && mode <= FAVOURITES_MODE) { @@ -723,16 +726,20 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); if(nC.nodeComesFromIncoming(node)) { ViewExtensionsKt.collectFlow(requireActivity(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE) { - setUnverifiedNodeUserName(incomingSharesViewModel.getState().getValue().getUnverifiedIncomingShares()); + if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() + && mMode == SHARED_ITEMS_MODE + && isNodeUnverified(state.getUnverifiedIncomingShares())) { + setUnverifiedNodeUserName(state.getUnverifiedIncomingShares()); hideNodeActions(); } return Unit.INSTANCE; }); } else { ViewExtensionsKt.collectFlow(requireActivity(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE) { - setUnverifiedNodeUserName(outgoingSharesViewModel.getState().getValue().getUnverifiedOutgoingShares()); + if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() + && mMode == SHARED_ITEMS_MODE + && isNodeUnverified(state.getUnverifiedOutgoingShares())) { + setUnverifiedNodeUserName(state.getUnverifiedOutgoingShares()); hideNodeActions(); } return Unit.INSTANCE; @@ -913,6 +920,12 @@ private void hideNodeActions() { contentView.findViewById(R.id.clear_share_option).setVisibility(View.GONE); } + private boolean isNodeUnverified(List shareDataList) { + unverifiedHandles.clear(); + shareDataList.forEach(shareData -> unverifiedHandles.add(shareData.getNodeHandle())); + return unverifiedHandles.contains(node.getHandle()); + } + @SuppressLint("NonConstantResourceId") @Override public void onClick(View v) { From e335e2ae680404611cea404a8d5ed90612d545db Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 13 Jan 2023 15:03:50 +0530 Subject: [PATCH 082/334] AND-15405 Code optimised & comments resolved --- .../app/main/adapters/MegaNodeAdapter.java | 46 +++++++++---------- .../NodeOptionsBottomSheetDialogFragment.java | 10 ++-- .../shares/incoming/IncomingSharesFragment.kt | 2 +- .../incoming/IncomingSharesViewModel.kt | 16 +++---- .../incoming/model/IncomingSharesState.kt | 4 +- .../shares/outgoing/OutgoingSharesFragment.kt | 2 +- .../outgoing/OutgoingSharesViewModel.kt | 9 +++- .../outgoing/model/OutgoingSharesState.kt | 3 +- .../incoming/IncomingSharesViewModelTest.kt | 2 +- 9 files changed, 49 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 9b1a4a35456..e418fc0d1d7 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -146,8 +146,8 @@ public class MegaNodeAdapter extends RecyclerView.Adapter unverifiedIncomingNodeHandles = new HashSet<>(); - private Set unverifiedOutgoingNodeHandles = new HashSet<>(); + private final Set unverifiedIncomingNodeHandles = new HashSet<>(); + private final Set unverifiedOutgoingNodeHandles = new HashSet<>(); private boolean isMandatoryFingerprintVerificationNeeded; public static class ViewHolderBrowser extends RecyclerView.ViewHolder { @@ -1092,8 +1092,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if (isMandatoryFingerprintVerificationNeeded && !unverifiedIncomingNodeHandles.isEmpty()) { - showUnverifiedNodeUi(unverifiedIncomingNodeHandles, node, holder); + if (isMandatoryFingerprintVerificationNeeded + && !unverifiedIncomingNodeHandles.isEmpty() + && unverifiedIncomingNodeHandles.contains(node.getHandle())) { + showUnverifiedNodeUi(holder); } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { @@ -1103,8 +1105,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else if (type == OUTGOING_SHARES_ADAPTER) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - if (isMandatoryFingerprintVerificationNeeded && !unverifiedOutgoingNodeHandles.isEmpty()) { - showUnverifiedNodeUi(unverifiedOutgoingNodeHandles, node, holder); + if (isMandatoryFingerprintVerificationNeeded + && !unverifiedOutgoingNodeHandles.isEmpty() + && unverifiedOutgoingNodeHandles.contains(node.getHandle())) { + showUnverifiedNodeUi(holder); } } } else { @@ -1524,36 +1528,32 @@ public void setMandatoryFingerprintVerificationValue(boolean isVerificationNeede /** * Adds unverified incoming nodes to Set * - * @param shares - List of incoming [ShareData] + * @param handles - List of incoming node handles */ - public void setUnverifiedIncomingNodes(List shares) { + public void setUnverifiedIncomingNodeHandles(List handles) { unverifiedIncomingNodeHandles.clear(); - shares.forEach(share -> unverifiedIncomingNodeHandles.add(share.getNodeHandle())); + unverifiedIncomingNodeHandles.addAll(handles); } /** * Adds unverified outgoing nodes to Set * - * @param shares - List of outgoing [ShareData] + * @param handles - List of outgoing node handles */ - public void setUnverifiedOutgoingNodes(List shares) { + public void setUnverifiedOutgoingNodeHandles(List handles) { unverifiedOutgoingNodeHandles.clear(); - shares.forEach(share -> unverifiedOutgoingNodeHandles.add(share.getNodeHandle())); + unverifiedOutgoingNodeHandles.addAll(handles); } /** - * Function to check if current node is unverified & show Ui items accordingly + * Function to show Unverified node UI items accordingly * - * @param unverifiedNodeHandles Unverified Node handles list - * @param currentNode Current node from adapter - * @param holder [ViewHolderBrowserList] + * @param holder [ViewHolderBrowserList] */ - private void showUnverifiedNodeUi(Set unverifiedNodeHandles, MegaNode currentNode, ViewHolderBrowserList holder) { - if (unverifiedNodeHandles.contains(currentNode.getHandle())) { - holder.permissionsIcon.setVisibility(View.VISIBLE); - holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); - holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); - holder.permissionsIcon.setImageResource(R.drawable.serious_warning); - } + private void showUnverifiedNodeUi(ViewHolderBrowserList holder) { + holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); + holder.permissionsIcon.setVisibility(View.VISIBLE); + holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } } diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index e49933a16a3..4dba22ed35c 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -728,7 +728,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat ViewExtensionsKt.collectFlow(requireActivity(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE - && isNodeUnverified(state.getUnverifiedIncomingShares())) { + && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedIncomingShares()); hideNodeActions(); } @@ -738,7 +738,7 @@ && isNodeUnverified(state.getUnverifiedIncomingShares())) { ViewExtensionsKt.collectFlow(requireActivity(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE - && isNodeUnverified(state.getUnverifiedOutgoingShares())) { + && isNodeUnverified(state.getUnVerifiedOutgoingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedOutgoingShares()); hideNodeActions(); } @@ -920,10 +920,8 @@ private void hideNodeActions() { contentView.findViewById(R.id.clear_share_option).setVisibility(View.GONE); } - private boolean isNodeUnverified(List shareDataList) { - unverifiedHandles.clear(); - shareDataList.forEach(shareData -> unverifiedHandles.add(shareData.getNodeHandle())); - return unverifiedHandles.contains(node.getHandle()); + private boolean isNodeUnverified(List shareDataList) { + return shareDataList.contains(node.getHandle()); } @SuppressLint("NonConstantResourceId") diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 84535cb35ec..6bcc1f19128 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -240,7 +240,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { hideActionMode() setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) - adapter?.setUnverifiedIncomingNodes(it.unverifiedIncomingShares) + adapter?.setUnverifiedIncomingNodeHandles(it.unVerifiedIncomingNodeHandles) updateNodes(it.nodes) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 7bcc95ae45b..1b52c2e1d7f 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -76,17 +76,15 @@ class IncomingSharesViewModel @Inject constructor( } viewModelScope.launch { - _state.update { - it.copy(unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder)) - } - } - - viewModelScope.launch { - val unverifiedIncomingNodes = _state.value.unverifiedIncomingShares + val unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder) + val handles = unverifiedIncomingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } + .mapNotNull { shareData -> + getNodeByHandle(shareData.nodeHandle)?.handle + } _state.update { - it.copy(unVerifiedIncomingNodes = unverifiedIncomingNodes) + it.copy(unverifiedIncomingShares = unverifiedIncomingShares, + unVerifiedIncomingNodeHandles = handles) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index e758b69c7b2..56ee6aaf12c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -16,7 +16,7 @@ import nz.mega.sdk.MegaNode * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedIncomingShares List of unverified incoming [ShareData] - * @param unVerifiedIncomingNodes List of unverified incoming [MegaNode] + * @param unVerifiedIncomingNodeHandles List of unverified incoming node handles */ data class IncomingSharesState( val incomingHandle: Long = -1L, @@ -28,7 +28,7 @@ data class IncomingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedIncomingShares: List = emptyList(), - val unVerifiedIncomingNodes: List = emptyList(), + val unVerifiedIncomingNodeHandles: List = emptyList(), ) { /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index cb1639c360d..4810411a01a 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -234,7 +234,7 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { hideActionMode() setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) - adapter?.setUnverifiedOutgoingNodes(it.unverifiedOutgoingShares) + adapter?.setUnverifiedOutgoingNodeHandles(it.unVerifiedOutgoingNodeHandles) updateNodes(it.nodes) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 365bcebd357..476ae514328 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -61,8 +61,15 @@ class OutgoingSharesViewModel @Inject constructor( } viewModelScope.launch { + val unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder) + val handles = unverifiedOutgoingShares + .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + .mapNotNull { shareData -> + getNodeByHandle(shareData.nodeHandle)?.handle + } _state.update { - it.copy(unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder)) + it.copy(unverifiedOutgoingShares = unverifiedOutgoingShares, + unVerifiedOutgoingNodeHandles = handles) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index ead168c2c42..b326a7e9987 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -16,7 +16,7 @@ import nz.mega.sdk.MegaNode * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] - * @param unVerifiedOutgoingNodes List of Unverified outgoing [MegaNode] + * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -28,6 +28,7 @@ data class OutgoingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), + val unVerifiedOutgoingNodeHandles: List = emptyList(), ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 493fd0719e1..d9dea96febf 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -526,7 +526,7 @@ class IncomingSharesViewModelTest { whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() initViewModel() - underTest.state.map { it.unVerifiedIncomingNodes }.distinctUntilChanged() + underTest.state.map { it.unverifiedIncomingShares }.distinctUntilChanged() .test { assertThat(awaitItem().size).isEqualTo(1) } From d4da26384212aaa8ba4dff17aa3a0c2b618b4e45 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 17 Jan 2023 18:31:26 +0530 Subject: [PATCH 083/334] AND-15405 Incoming nodes bug fixed --- .../shares/incoming/IncomingSharesViewModel.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 1b52c2e1d7f..36e0484a396 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -77,19 +77,24 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { val unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder) + val unverifiedIncomingNodes = unverifiedIncomingShares + .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + .mapNotNull { shareData -> + getNodeByHandle(shareData.nodeHandle) + } val handles = unverifiedIncomingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle)?.handle } _state.update { - it.copy(unverifiedIncomingShares = unverifiedIncomingShares, + it.copy(nodes = unverifiedIncomingNodes, + unverifiedIncomingShares = unverifiedIncomingShares, unVerifiedIncomingNodeHandles = handles) } } } - /** * Refresh incoming shares node */ From 4a60c496f5d72ce1fee77503d88fcfcc872f0e68 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 23 Jan 2023 17:16:02 +1300 Subject: [PATCH 084/334] AND-15464 Unverified incoming nodes displayed under incoming shares tab --- .../incoming/IncomingSharesViewModelTest.kt | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index d9dea96febf..6baf6512a1e 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.AuthorizeNode @@ -27,6 +28,7 @@ import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import nz.mega.sdk.MegaNode +import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test @@ -74,6 +76,11 @@ class IncomingSharesViewModelTest { initViewModel() } + @After + fun tearDown() { + Dispatchers.resetMain() + } + private fun initViewModel() { underTest = IncomingSharesViewModel( getNodeByHandle, @@ -243,13 +250,13 @@ class IncomingSharesViewModelTest { @Test fun `test that is invalid handle is set to false when call set incoming tree depth with valid handle`() = runTest { - whenever(getIncomingSharesChildrenNode(any())).thenReturn(mock()) + whenever(getIncomingSharesChildrenNode(any())).thenReturn(listOf(mock())) whenever(getNodeByHandle(any())).thenReturn(mock()) underTest.state.map { it.isInvalidHandle }.distinctUntilChanged() .test { assertThat(awaitItem()).isEqualTo(true) - underTest.setIncomingTreeDepth(any(), 123456789L) + underTest.setIncomingTreeDepth(1, 123456789L) assertThat(awaitItem()).isEqualTo(false) } } @@ -257,15 +264,15 @@ class IncomingSharesViewModelTest { @Test fun `test that is invalid handle is set to true when call set incoming tree depth with invalid handle`() = runTest { - whenever(getIncomingSharesChildrenNode(any())).thenReturn(mock()) + whenever(getIncomingSharesChildrenNode(any())).thenReturn(listOf(mock())) whenever(getNodeByHandle(any())).thenReturn(mock()) underTest.state.map { it.isInvalidHandle }.distinctUntilChanged() .test { assertThat(awaitItem()).isEqualTo(true) - underTest.setIncomingTreeDepth(any(), 123456789L) + underTest.setIncomingTreeDepth(1, 123456789L) assertThat(awaitItem()).isEqualTo(false) - underTest.setIncomingTreeDepth(any(), -1L) + underTest.setIncomingTreeDepth(1, -1L) assertThat(awaitItem()).isEqualTo(true) } } @@ -274,18 +281,18 @@ class IncomingSharesViewModelTest { @Test fun `test that is invalid handle is set to true when cannot retrieve node`() = runTest { - whenever(getIncomingSharesChildrenNode(any())).thenReturn(mock()) + whenever(getIncomingSharesChildrenNode(any())).thenReturn(listOf(mock())) whenever(getNodeByHandle(any())).thenReturn(mock()) underTest.state.map { it.isInvalidHandle }.distinctUntilChanged() .test { assertThat(awaitItem()).isEqualTo(true) - underTest.setIncomingTreeDepth(any(), 123456789L) + underTest.setIncomingTreeDepth(1, 123456789L) assertThat(awaitItem()).isEqualTo(false) whenever(getNodeByHandle(any())).thenReturn(null) - underTest.setIncomingTreeDepth(any(), 987654321L) + underTest.setIncomingTreeDepth(1, 987654321L) assertThat(awaitItem()).isEqualTo(true) } } From af019743db3a854698054a7bfed99d4b548f4426 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 23 Jan 2023 16:13:46 +0530 Subject: [PATCH 085/334] AND-15405 Outgoing folder name displayed instead of undecrypted folder --- .../android/app/main/adapters/MegaNodeAdapter.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index e418fc0d1d7..d0890ec4f7e 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1095,7 +1095,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { if (isMandatoryFingerprintVerificationNeeded && !unverifiedIncomingNodeHandles.isEmpty() && unverifiedIncomingNodeHandles.contains(node.getHandle())) { - showUnverifiedNodeUi(holder); + showUnverifiedNodeUi(holder, true); } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { @@ -1108,7 +1108,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { if (isMandatoryFingerprintVerificationNeeded && !unverifiedOutgoingNodeHandles.isEmpty() && unverifiedOutgoingNodeHandles.contains(node.getHandle())) { - showUnverifiedNodeUi(holder); + showUnverifiedNodeUi(holder, false); } } } else { @@ -1548,10 +1548,14 @@ public void setUnverifiedOutgoingNodeHandles(List handles) { /** * Function to show Unverified node UI items accordingly * - * @param holder [ViewHolderBrowserList] + * @param holder [ViewHolderBrowserList] + * @param isIncomingNode boolean to indicate if the node is incoming so that + * "Undecrypted folder" is displayed instead of node name */ - private void showUnverifiedNodeUi(ViewHolderBrowserList holder) { - holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + private void showUnverifiedNodeUi(ViewHolderBrowserList holder, Boolean isIncomingNode) { + if (isIncomingNode) { + holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + } holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setVisibility(View.VISIBLE); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); From 47ca2c926b0b211e6791420d26b3cd95c757db6e Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 23 Jan 2023 16:43:18 +0530 Subject: [PATCH 086/334] unnecessary change reverted --- .../app/presentation/shares/incoming/IncomingSharesViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 36e0484a396..a458941ff23 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -95,6 +95,7 @@ class IncomingSharesViewModel @Inject constructor( } } + /** * Refresh incoming shares node */ From 5a21e6c057f38ab76df3cd6258ddfd546654e7de Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 30 Jan 2023 17:48:33 +0530 Subject: [PATCH 087/334] Add undecrypted files string to the recent items in cloud drive --- .../recent/RecentsBucketViewModel.kt | 10 ++++ .../recentactions/RecentActionsAdapter.kt | 27 +++++++-- .../recentactions/RecentActionsFragment.kt | 42 ++++++++----- app/src/main/res/values/strings.xml | 59 ++++++++++++++++++- .../privacy/android/data/mapper/NodeMapper.kt | 2 + .../data/model/node/DefaultFileNode.kt | 1 + .../data/model/node/DefaultFolderNode.kt | 1 + .../android/domain/entity/node/FileNode.kt | 5 ++ .../android/domain/entity/node/FolderNode.kt | 5 ++ 9 files changed, 129 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt index cdb548195dc..1e5d016e98a 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt @@ -20,6 +20,7 @@ import mega.privacy.android.domain.usecase.UpdateRecentAction import mega.privacy.android.app.fragments.homepage.NodeItem import mega.privacy.android.data.qualifier.MegaApi import mega.privacy.android.domain.entity.RecentActionBucket +import mega.privacy.android.domain.usecase.AreCredentialsVerified import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaNode import timber.log.Timber @@ -35,6 +36,7 @@ class RecentsBucketViewModel @Inject constructor( private val updateRecentAction: UpdateRecentAction, private val getRecentActionNodes: GetRecentActionNodes, monitorNodeUpdates: MonitorNodeUpdates, + private val areCredentialsVerifiedUseCase: AreCredentialsVerified, ) : ViewModel() { private val _actionMode = MutableLiveData() @@ -67,6 +69,10 @@ class RecentsBucketViewModel @Inject constructor( */ val shouldCloseFragment: LiveData = _shouldCloseFragment + private val _areCredentialsVerified: MutableLiveData = MutableLiveData(false) + + val areCredentialsVerified: LiveData = _areCredentialsVerified + /** * True if the parent of the bucket is an incoming shares */ @@ -92,6 +98,10 @@ class RecentsBucketViewModel @Inject constructor( clearSelection() } } + + viewModelScope.launch { + _areCredentialsVerified.postValue(areCredentialsVerifiedUseCase.invoke(megaApi.myUser.email)) + } } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index 63871805bd6..71d39934218 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -72,6 +72,8 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter - // If only one element in the bucket - if (item.bucket.nodes.size == 1) { - lifecycleScope.launch { - val node = item.bucket.nodes[0] - viewModel.getMegaNode(node.id.longValue)?.let { - openFile(position, it) + + if (!item.bucket.nodes[0].isNodeKeyDecrypted && !megaApi.areCredentialsVerified(megaApi.myUser)) { + Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { + putExtra(Constants.EMAIL, item.bucket.userEmail) + requireActivity().startActivity(this) + } + } else { + // If only one element in the bucket + if (item.bucket.nodes.size == 1) { + lifecycleScope.launch { + val node = item.bucket.nodes[0] + viewModel.getMegaNode(node.id.longValue)?.let { + openFile(position, it) + } } } - } - // If more element in the bucket - else { - viewModel.select(item) - val currentDestination = - Navigation.findNavController(requireView()).currentDestination - if (currentDestination != null && currentDestination.id == R.id.homepageFragment) { - Navigation.findNavController(requireView()) - .navigate(HomepageFragmentDirections.actionHomepageToRecentBucket(), - NavOptions.Builder().build()) + // If more element in the bucket + else { + viewModel.select(item) + val currentDestination = + Navigation.findNavController(requireView()).currentDestination + if (currentDestination != null && currentDestination.id == R.id.homepageFragment) { + Navigation.findNavController(requireView()) + .navigate(HomepageFragmentDirections.actionHomepageToRecentBucket(), + NavOptions.Builder().build()) + } } } } @@ -167,6 +176,7 @@ class RecentActionsFragment : Fragment() { listView.addItemDecoration(HeaderItemDecoration(requireContext())) listView.clipToPadding = false listView.itemAnimator = DefaultItemAnimator() + adapter.setIsUserVerified(megaApi.areCredentialsVerified(megaApi.myUser)) } /** diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 87336726d87..db8b404b1b0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -790,7 +790,7 @@ Save - The Recovery key has been successfully copied + Recovery key copied to clipboard. Save it to a safe place where you can easily access later. Change @@ -4138,7 +4138,60 @@ Bonus expires in %1$d day Bonus expires in %1$d days + + Description + + Remove from album? + + To enable camera uploads, grant MEGA access to your photos and other media on your device. + + Grant access + + Don’t grant + + + Removed %d item from “%s” + Removed %d items from “%s” + + + One-off meeting + + Resume video? + + %1$s will resume from %2$s + + Resume + + Restart + + [A]%s [/A][B]invited you to a meeting scheduled for:[/B] + + [A]%s [/A][B]cancelled the meeting scheduled for:[/B] + + [A]%s [/A][B]updated the meeting name from “%s” to [/B]“[A]%s[/A]“ + + [A]%s [/A][B]updated the meeting date[/B] + + [A]%s [/A][B]updated the meeting time[/B] + + [A]%s [/A][B]updated the meeting description[/B] + + [A]%s [/A][B]updated the meeting details scheduled for:[/B] + + Access Denied + + You denied MEGA access to your device’s storage and media files. If you’d like to continue sharing, allow MEGA permission. + + Allow permission + + Don’t allow Security upgrade - Your account’s security is now being upgraded. This will happen only once. If you have seen this message for this account before, press Cancel. - You are currently sharing the following folders: %s + Your account’s security is now being upgraded. This will happen only once. If you’ve seen this message for this account before, tap Cancel. + You’re currently sharing the following folders: %s + + + + [Undecrypted file] + [Undecrypted files] + \ No newline at end of file diff --git a/data/src/main/java/mega/privacy/android/data/mapper/NodeMapper.kt b/data/src/main/java/mega/privacy/android/data/mapper/NodeMapper.kt index e1679c13cab..1f2eb91ab28 100644 --- a/data/src/main/java/mega/privacy/android/data/mapper/NodeMapper.kt +++ b/data/src/main/java/mega/privacy/android/data/mapper/NodeMapper.kt @@ -54,6 +54,7 @@ internal suspend fun toNode( isShared = megaNode.isOutShare, isPendingShare = isPendingShare(megaNode), device = megaNode.deviceId, + isNodeKeyDecrypted = megaNode.isNodeKeyDecrypted, ) } else { DefaultFileNode( @@ -72,5 +73,6 @@ internal suspend fun toNode( isTakenDown = megaNode.isTakenDown, isIncomingShare = megaNode.isInShare, fingerprint = megaNode.fingerprint, + isNodeKeyDecrypted = megaNode.isNodeKeyDecrypted, ) } diff --git a/data/src/main/java/mega/privacy/android/data/model/node/DefaultFileNode.kt b/data/src/main/java/mega/privacy/android/data/model/node/DefaultFileNode.kt index c489315acfe..dbaceab357b 100644 --- a/data/src/main/java/mega/privacy/android/data/model/node/DefaultFileNode.kt +++ b/data/src/main/java/mega/privacy/android/data/model/node/DefaultFileNode.kt @@ -20,4 +20,5 @@ internal data class DefaultFileNode( override val isTakenDown: Boolean, override val isIncomingShare: Boolean, override val fingerprint: String?, + override val isNodeKeyDecrypted: Boolean, ) : FileNode diff --git a/data/src/main/java/mega/privacy/android/data/model/node/DefaultFolderNode.kt b/data/src/main/java/mega/privacy/android/data/model/node/DefaultFolderNode.kt index cca0c4757da..348a9f4e92a 100644 --- a/data/src/main/java/mega/privacy/android/data/model/node/DefaultFolderNode.kt +++ b/data/src/main/java/mega/privacy/android/data/model/node/DefaultFolderNode.kt @@ -20,4 +20,5 @@ internal data class DefaultFolderNode( override val isShared: Boolean, override val isPendingShare: Boolean, override val device: String?, + override val isNodeKeyDecrypted: Boolean, ) : FolderNode \ No newline at end of file diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FileNode.kt b/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FileNode.kt index 4dff47573d9..d0c6efa4eca 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FileNode.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FileNode.kt @@ -30,4 +30,9 @@ interface FileNode : UnTypedNode { * Fingerprint */ val fingerprint: String? + + /** + * Is node key decrypted by verification from owner + */ + val isNodeKeyDecrypted: Boolean } diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FolderNode.kt b/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FolderNode.kt index 7954ef08f91..b556cf694d5 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FolderNode.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FolderNode.kt @@ -34,4 +34,9 @@ interface FolderNode : UnTypedNode { * Number of child files */ val childFileCount: Int + + /** + * Is node key decrypted by verification from owner + */ + val isNodeKeyDecrypted: Boolean } From 5c4ca24af17bdc94ede68a15a9a597d112e87d98 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 30 Jan 2023 21:15:56 +0530 Subject: [PATCH 088/334] Resolve Code comments Contact verification screen opened on Unverified incoming node tap action NO_KEY name for Unverified incoming node fixed Code optimised to use viewmodel --- .../recent/RecentsBucketViewModel.kt | 9 +--- .../app/main/adapters/MegaNodeAdapter.java | 4 +- .../NodeOptionsBottomSheetDialogFragment.java | 6 ++- .../recentactions/RecentActionsAdapter.kt | 2 +- .../recentactions/RecentActionsFragment.kt | 10 +++- .../recentactions/RecentActionsViewModel.kt | 13 ++++- .../recentactions/model/RecentActionsState.kt | 2 + .../shares/incoming/IncomingSharesFragment.kt | 50 ++++++++++++------- .../RecentActionsViewModelTest.kt | 4 ++ 9 files changed, 67 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt index 1e5d016e98a..8199086b13a 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt @@ -36,7 +36,6 @@ class RecentsBucketViewModel @Inject constructor( private val updateRecentAction: UpdateRecentAction, private val getRecentActionNodes: GetRecentActionNodes, monitorNodeUpdates: MonitorNodeUpdates, - private val areCredentialsVerifiedUseCase: AreCredentialsVerified, ) : ViewModel() { private val _actionMode = MutableLiveData() @@ -69,9 +68,7 @@ class RecentsBucketViewModel @Inject constructor( */ val shouldCloseFragment: LiveData = _shouldCloseFragment - private val _areCredentialsVerified: MutableLiveData = MutableLiveData(false) - - val areCredentialsVerified: LiveData = _areCredentialsVerified + private val _areCredentialsVerified: MutableStateFlow = MutableStateFlow(false) /** * True if the parent of the bucket is an incoming shares @@ -98,10 +95,6 @@ class RecentsBucketViewModel @Inject constructor( clearSelection() } } - - viewModelScope.launch { - _areCredentialsVerified.postValue(areCredentialsVerifiedUseCase.invoke(megaApi.myUser.email)) - } } /** diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index d0890ec4f7e..23d08a57a77 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1093,8 +1093,8 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } if (isMandatoryFingerprintVerificationNeeded - && !unverifiedIncomingNodeHandles.isEmpty() - && unverifiedIncomingNodeHandles.contains(node.getHandle())) { + && !node.isNodeKeyDecrypted() + && !megaApi.areCredentialsVerified(megaApi.getMyUser())) { showUnverifiedNodeUi(holder, true); } holder.permissionsIcon.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 4dba22ed35c..48bd00b5606 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -728,7 +728,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat ViewExtensionsKt.collectFlow(requireActivity(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE - && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { + && isIncomingNodeVerified()) { setUnverifiedNodeUserName(state.getUnverifiedIncomingShares()); hideNodeActions(); } @@ -924,6 +924,10 @@ private boolean isNodeUnverified(List shareDataList) { return shareDataList.contains(node.getHandle()); } + private boolean isIncomingNodeVerified() { + return !node.isNodeKeyDecrypted() && !megaApi.areCredentialsVerified(megaApi.getMyUser()); + } + @SuppressLint("NonConstantResourceId") @Override public void onClick(View v) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index 71d39934218..ff5e41f53fd 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -354,7 +354,7 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter - if (!item.bucket.nodes[0].isNodeKeyDecrypted && !megaApi.areCredentialsVerified(megaApi.myUser)) { + if (!item.bucket.nodes[0].isNodeKeyDecrypted && !viewModel.state.value.areUserCredentialsVerified) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, item.bucket.userEmail) requireActivity().startActivity(this) @@ -176,7 +176,13 @@ class RecentActionsFragment : Fragment() { listView.addItemDecoration(HeaderItemDecoration(requireContext())) listView.clipToPadding = false listView.itemAnimator = DefaultItemAnimator() - adapter.setIsUserVerified(megaApi.areCredentialsVerified(megaApi.myUser)) + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewModel.state.collect { + adapter.setAreUserCredentialsVerified(it.areUserCredentialsVerified) + } + } + } } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index 4d5d34e5950..c4dd65a0b11 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -11,7 +11,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetParentMegaNode -import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.app.domain.usecase.IsPendingShare import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates import mega.privacy.android.app.presentation.recentactions.model.RecentActionItemType @@ -19,7 +18,9 @@ import mega.privacy.android.app.presentation.recentactions.model.RecentActionsSh import mega.privacy.android.app.presentation.recentactions.model.RecentActionsState import mega.privacy.android.domain.entity.RecentActionBucket import mega.privacy.android.domain.entity.contacts.ContactItem +import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails +import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts import mega.privacy.android.domain.usecase.MonitorHideRecentActivity import mega.privacy.android.domain.usecase.SetHideRecentActivity @@ -35,6 +36,7 @@ import javax.inject.Inject * @param getVisibleContacts * @param setHideRecentActivity * @param monitorNodeUpdates + * @param areCredentialsVerified */ @HiltViewModel class RecentActionsViewModel @Inject constructor( @@ -47,6 +49,7 @@ class RecentActionsViewModel @Inject constructor( private val getParentMegaNode: GetParentMegaNode, monitorHideRecentActivity: MonitorHideRecentActivity, monitorNodeUpdates: MonitorNodeUpdates, + val areCredentialsVerified: AreCredentialsVerified, ) : ViewModel() { private var _buckets = listOf() @@ -84,6 +87,14 @@ class RecentActionsViewModel @Inject constructor( } } + fun checkIfUserCredentialsAreVerified(userEmail: String) { + viewModelScope.launch { + _state.update { + it.copy(areUserCredentialsVerified = areCredentialsVerified(userEmail)) + } + } + } + /** * Set the selected recent actions bucket and current recent actions bucket list * diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt index d0bd2cfce93..6a3f712ef5f 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt @@ -5,8 +5,10 @@ package mega.privacy.android.app.presentation.recentactions.model * * @param recentActionItems list of recent action items * @param hideRecentActivity true if recent activity should be hidden + * @param areUserCredentialsVerified true if user credentials are verified from Mega Api */ data class RecentActionsState( val recentActionItems: List = emptyList(), val hideRecentActivity: Boolean = false, + val areUserCredentialsVerified: Boolean = false, ) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 6bcc1f19128..c82a1a60c51 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -1,5 +1,6 @@ package mega.privacy.android.app.presentation.shares.incoming +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.Menu @@ -17,6 +18,7 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.R import mega.privacy.android.app.components.NewGridRecyclerView import mega.privacy.android.app.main.adapters.MegaNodeAdapter +import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.manager.model.Tab import mega.privacy.android.app.presentation.shares.MegaNodeBaseFragment @@ -26,6 +28,7 @@ import mega.privacy.android.app.utils.ColorUtils.setImageViewAlphaIfDark import mega.privacy.android.app.utils.Constants import mega.privacy.android.app.utils.Constants.ORDER_CLOUD import mega.privacy.android.app.utils.Constants.ORDER_OTHERS +import mega.privacy.android.app.utils.ContactUtil import mega.privacy.android.app.utils.MegaNodeUtil.allHaveFullAccess import mega.privacy.android.app.utils.MegaNodeUtil.areAllFileNodesAndNotTakenDown import mega.privacy.android.app.utils.MegaNodeUtil.areAllNotTakenDown @@ -95,27 +98,38 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { override fun itemClick(position: Int) { val actualPosition = position - 1 - when { - // select mode - adapter?.isMultipleSelect == true -> { - adapter?.toggleSelection(position) - val selectedNodes = adapter?.selectedNodes - if ((selectedNodes?.size ?: 0) > 0) - updateActionModeTitle() + if (!state().nodes[actualPosition].isNodeKeyDecrypted && + !megaApi.areCredentialsVerified(megaApi.myUser) + ) { + Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { + putExtra(Constants.EMAIL, + ContactUtil.getContactEmailDB(state().nodes[actualPosition].owner)) + requireActivity().startActivity(this) } + } else { + when { + // select mode + adapter?.isMultipleSelect == true -> { + adapter?.toggleSelection(position) + val selectedNodes = adapter?.selectedNodes + if ((selectedNodes?.size ?: 0) > 0) + updateActionModeTitle() + } - // click on a folder - state().nodes[actualPosition].isFolder -> - navigateToFolder(state().nodes[actualPosition]) - - // click on a file - else -> - openFile( - state().nodes[actualPosition], - Constants.INCOMING_SHARES_ADAPTER, - actualPosition - ) + // click on a folder + state().nodes[actualPosition].isFolder -> + navigateToFolder(state().nodes[actualPosition]) + + // click on a file + else -> + openFile( + state().nodes[actualPosition], + Constants.INCOMING_SHARES_ADAPTER, + actualPosition + ) + } } + } override fun navigateToFolder(node: MegaNode) { diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt index 990cd56ca10..0c374150173 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt @@ -24,6 +24,7 @@ import mega.privacy.android.domain.entity.contacts.ContactItem import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.entity.node.TypedFileNode +import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -69,6 +70,8 @@ class RecentActionsViewModelTest { } private val monitorNodeUpdates = FakeMonitorUpdates() + private val areCredentialsVerified = mock() + private val node: TypedFileNode = mock { on { id }.thenReturn(NodeId(123)) } @@ -114,6 +117,7 @@ class RecentActionsViewModelTest { getParentMegaNode, monitorHideRecentActivity, monitorNodeUpdates, + areCredentialsVerified ) } From ab2196f6800dd47b677f68e16d578eaebbe99d20 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 30 Jan 2023 21:44:49 +0530 Subject: [PATCH 089/334] Test failure fixed --- .../data/repository/DefaultMediaPlayerRepositoryTest.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/data/src/test/java/mega/privacy/android/data/repository/DefaultMediaPlayerRepositoryTest.kt b/data/src/test/java/mega/privacy/android/data/repository/DefaultMediaPlayerRepositoryTest.kt index 86c692547f2..2f6f65ab1ff 100644 --- a/data/src/test/java/mega/privacy/android/data/repository/DefaultMediaPlayerRepositoryTest.kt +++ b/data/src/test/java/mega/privacy/android/data/repository/DefaultMediaPlayerRepositoryTest.kt @@ -73,7 +73,7 @@ class DefaultMediaPlayerRepositoryTest { private val expectedType = StaticImageFileTypeInfo(mimeType = "", extension = "image") private val expectedFileMegaNode = createMegaNode(false) private val expectedFolderMegaNode = createMegaNode(true) - + private val isNodeKetDecrypted = false private val expectedMediaId: Long = 1234567 private val expectedTotalDuration: Long = 200000 private val expectedCurrentPosition: Long = 16000 @@ -290,7 +290,8 @@ class DefaultMediaPlayerRepositoryTest { isIncomingShare = expectedIncomingShare, isShared = expectedInShared, isPendingShare = expectedIsPendingShare, - device = expectedDevice + device = expectedDevice, + isNodeKeyDecrypted = isNodeKetDecrypted, ) private fun createTypedFileNode() = DefaultFileNode( @@ -308,7 +309,8 @@ class DefaultMediaPlayerRepositoryTest { modificationTime = expectedModificationTime, fingerprint = expectedFingerprint, thumbnailPath = expectedThumbnailPath, - type = expectedType + type = expectedType, + isNodeKeyDecrypted = isNodeKetDecrypted, ) private suspend fun initTestConditions(megaNode: MegaNode, typeInfo: FileTypeInfo) { From 76015151cca9457b436683b632c888c425b6ee44 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 31 Jan 2023 11:43:41 +0530 Subject: [PATCH 090/334] Code comments fixed. --- .../app/fragments/recent/RecentsBucketViewModel.kt | 3 --- .../android/app/main/adapters/MegaNodeAdapter.java | 8 ++++---- .../NodeOptionsBottomSheetDialogFragment.java | 2 +- .../recentactions/RecentActionsFragment.kt | 4 ++-- .../recentactions/RecentActionsViewModel.kt | 11 ----------- .../recentactions/model/RecentActionsState.kt | 2 -- .../shares/incoming/IncomingSharesFragment.kt | 2 +- .../recentactions/RecentActionsViewModelTest.kt | 3 --- 8 files changed, 8 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt index 8199086b13a..cdb548195dc 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt @@ -20,7 +20,6 @@ import mega.privacy.android.domain.usecase.UpdateRecentAction import mega.privacy.android.app.fragments.homepage.NodeItem import mega.privacy.android.data.qualifier.MegaApi import mega.privacy.android.domain.entity.RecentActionBucket -import mega.privacy.android.domain.usecase.AreCredentialsVerified import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaNode import timber.log.Timber @@ -68,8 +67,6 @@ class RecentsBucketViewModel @Inject constructor( */ val shouldCloseFragment: LiveData = _shouldCloseFragment - private val _areCredentialsVerified: MutableStateFlow = MutableStateFlow(false) - /** * True if the parent of the bucket is an incoming shares */ diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 23d08a57a77..9c9fc9c335a 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1092,10 +1092,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if (isMandatoryFingerprintVerificationNeeded - && !node.isNodeKeyDecrypted() - && !megaApi.areCredentialsVerified(megaApi.getMyUser())) { - showUnverifiedNodeUi(holder, true); + if (isMandatoryFingerprintVerificationNeeded) { + if (!node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getMyUser())) { + showUnverifiedNodeUi(holder, true); + } } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 48bd00b5606..d3188607ad5 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -925,7 +925,7 @@ private boolean isNodeUnverified(List shareDataList) { } private boolean isIncomingNodeVerified() { - return !node.isNodeKeyDecrypted() && !megaApi.areCredentialsVerified(megaApi.getMyUser()); + return !node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getMyUser()); } @SuppressLint("NonConstantResourceId") diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt index 6432d311599..9797d6d253b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt @@ -130,7 +130,7 @@ class RecentActionsFragment : Fragment() { private fun initAdapter() { adapter.setOnItemClickListener { item, position -> - if (!item.bucket.nodes[0].isNodeKeyDecrypted && !viewModel.state.value.areUserCredentialsVerified) { + if (!item.bucket.nodes[0].isNodeKeyDecrypted || !megaApi.areCredentialsVerified(megaApi.myUser)) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, item.bucket.userEmail) requireActivity().startActivity(this) @@ -179,7 +179,7 @@ class RecentActionsFragment : Fragment() { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { viewModel.state.collect { - adapter.setAreUserCredentialsVerified(it.areUserCredentialsVerified) + adapter.setAreUserCredentialsVerified(megaApi.areCredentialsVerified(megaApi.myUser)) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index c4dd65a0b11..b54585fb3c0 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -18,7 +18,6 @@ import mega.privacy.android.app.presentation.recentactions.model.RecentActionsSh import mega.privacy.android.app.presentation.recentactions.model.RecentActionsState import mega.privacy.android.domain.entity.RecentActionBucket import mega.privacy.android.domain.entity.contacts.ContactItem -import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -36,7 +35,6 @@ import javax.inject.Inject * @param getVisibleContacts * @param setHideRecentActivity * @param monitorNodeUpdates - * @param areCredentialsVerified */ @HiltViewModel class RecentActionsViewModel @Inject constructor( @@ -49,7 +47,6 @@ class RecentActionsViewModel @Inject constructor( private val getParentMegaNode: GetParentMegaNode, monitorHideRecentActivity: MonitorHideRecentActivity, monitorNodeUpdates: MonitorNodeUpdates, - val areCredentialsVerified: AreCredentialsVerified, ) : ViewModel() { private var _buckets = listOf() @@ -87,14 +84,6 @@ class RecentActionsViewModel @Inject constructor( } } - fun checkIfUserCredentialsAreVerified(userEmail: String) { - viewModelScope.launch { - _state.update { - it.copy(areUserCredentialsVerified = areCredentialsVerified(userEmail)) - } - } - } - /** * Set the selected recent actions bucket and current recent actions bucket list * diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt index 6a3f712ef5f..d0bd2cfce93 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt @@ -5,10 +5,8 @@ package mega.privacy.android.app.presentation.recentactions.model * * @param recentActionItems list of recent action items * @param hideRecentActivity true if recent activity should be hidden - * @param areUserCredentialsVerified true if user credentials are verified from Mega Api */ data class RecentActionsState( val recentActionItems: List = emptyList(), val hideRecentActivity: Boolean = false, - val areUserCredentialsVerified: Boolean = false, ) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index c82a1a60c51..1d472c30e11 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -98,7 +98,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { override fun itemClick(position: Int) { val actualPosition = position - 1 - if (!state().nodes[actualPosition].isNodeKeyDecrypted && + if (!state().nodes[actualPosition].isNodeKeyDecrypted || !megaApi.areCredentialsVerified(megaApi.myUser) ) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt index 0c374150173..7e63350e1a3 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt @@ -70,8 +70,6 @@ class RecentActionsViewModelTest { } private val monitorNodeUpdates = FakeMonitorUpdates() - private val areCredentialsVerified = mock() - private val node: TypedFileNode = mock { on { id }.thenReturn(NodeId(123)) } @@ -117,7 +115,6 @@ class RecentActionsViewModelTest { getParentMegaNode, monitorHideRecentActivity, monitorNodeUpdates, - areCredentialsVerified ) } From 4d750e2e34d8068a4083675c4d463a2acca160d2 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 31 Jan 2023 13:28:02 +0530 Subject: [PATCH 091/334] areCredentialsVerified checked with node owner --- .../app/main/adapters/MegaNodeAdapter.java | 3 +- .../NodeOptionsBottomSheetDialogFragment.java | 2 +- .../recentactions/RecentActionsAdapter.kt | 2 +- .../recentactions/RecentActionsFragment.kt | 31 ++++++++++++++----- .../shares/incoming/IncomingSharesFragment.kt | 2 +- 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 9c9fc9c335a..7240539d999 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -97,6 +97,7 @@ import mega.privacy.android.app.presentation.shares.links.LinksFragment; import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesFragment; import mega.privacy.android.app.utils.ColorUtils; +import mega.privacy.android.app.utils.ContactUtil; import mega.privacy.android.app.utils.MegaNodeUtil; import mega.privacy.android.app.utils.NodeTakenDownDialogListener; import mega.privacy.android.app.utils.ThumbnailUtils; @@ -1093,7 +1094,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } if (isMandatoryFingerprintVerificationNeeded) { - if (!node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getMyUser())) { + if (!node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getContact(String.valueOf(node.getOwner())))) { showUnverifiedNodeUi(holder, true); } } diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index d3188607ad5..1d455f51356 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -925,7 +925,7 @@ private boolean isNodeUnverified(List shareDataList) { } private boolean isIncomingNodeVerified() { - return !node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getMyUser()); + return !node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getContact(String.valueOf(node.getOwner()))); } @SuppressLint("NonConstantResourceId") diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index ff5e41f53fd..64e47e8b56a 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -193,7 +193,7 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter - if (!item.bucket.nodes[0].isNodeKeyDecrypted || !megaApi.areCredentialsVerified(megaApi.myUser)) { + if (!item.bucket.nodes[0].isNodeKeyDecrypted || + !megaApi.areCredentialsVerified(megaApi.getContact(item.bucket.nodes[0].id.longValue.toString())) + ) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, item.bucket.userEmail) requireActivity().startActivity(this) @@ -176,13 +179,6 @@ class RecentActionsFragment : Fragment() { listView.addItemDecoration(HeaderItemDecoration(requireContext())) listView.clipToPadding = false listView.itemAnimator = DefaultItemAnimator() - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewModel.state.collect { - adapter.setAreUserCredentialsVerified(megaApi.areCredentialsVerified(megaApi.myUser)) - } - } - } } /** @@ -192,10 +188,29 @@ class RecentActionsFragment : Fragment() { */ private fun setRecentActions(recentActionItems: List) { adapter.setItems(recentActionItems) + adapter.setAreUserCredentialsVerified( + megaApi.areCredentialsVerified( + megaApi.getContact(getUserEmail(recentActionItems)) + ) + ) listView.layoutManager = TopSnappedStickyLayoutManager(requireContext()) { recentActionItems } } + /** + * Function to get user email from the adapter data set only if it contains bucket otherwise returns blank + * + * @param recentActionItems List of [RecentActionItemType] which is provided to adapter + */ + private fun getUserEmail(recentActionItems: List): String { + for (itemType: RecentActionItemType in recentActionItems) { + if (itemType is RecentActionItemType.Item) { + return itemType.bucket.userEmail + } + } + return "" + } + /** * Display the recent actions activity. * Hide the activity if the setting to hide is enabled, and shows it if the diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 1d472c30e11..9df3393bb35 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -99,7 +99,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { val actualPosition = position - 1 if (!state().nodes[actualPosition].isNodeKeyDecrypted || - !megaApi.areCredentialsVerified(megaApi.myUser) + !megaApi.areCredentialsVerified(megaApi.getContact(state().nodes[actualPosition].owner.toString())) ) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, From 9582bf5ef2e7b503f5159fe673d1ae8d84b65a95 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 31 Jan 2023 16:29:06 +0530 Subject: [PATCH 092/334] Code comments fixed --- .../recentactions/RecentActionsAdapter.kt | 11 +------ .../recentactions/RecentActionsFragment.kt | 6 ---- .../recentactions/RecentActionsViewModel.kt | 29 ++++++++++++++----- .../model/RecentActionItemType.kt | 1 + .../RecentActionsViewModelTest.kt | 3 ++ 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index 64e47e8b56a..5b7003d82a6 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -193,7 +193,7 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter) { adapter.setItems(recentActionItems) - adapter.setAreUserCredentialsVerified( - megaApi.areCredentialsVerified( - megaApi.getContact(getUserEmail(recentActionItems)) - ) - ) listView.layoutManager = TopSnappedStickyLayoutManager(requireContext()) { recentActionItems } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index b54585fb3c0..db4584343c7 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -18,6 +18,7 @@ import mega.privacy.android.app.presentation.recentactions.model.RecentActionsSh import mega.privacy.android.app.presentation.recentactions.model.RecentActionsState import mega.privacy.android.domain.entity.RecentActionBucket import mega.privacy.android.domain.entity.contacts.ContactItem +import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -35,6 +36,7 @@ import javax.inject.Inject * @param getVisibleContacts * @param setHideRecentActivity * @param monitorNodeUpdates + * @param areCredentialsVerified */ @HiltViewModel class RecentActionsViewModel @Inject constructor( @@ -47,6 +49,7 @@ class RecentActionsViewModel @Inject constructor( private val getParentMegaNode: GetParentMegaNode, monitorHideRecentActivity: MonitorHideRecentActivity, monitorNodeUpdates: MonitorNodeUpdates, + val areCredentialsVerified: AreCredentialsVerified, ) : ViewModel() { private var _buckets = listOf() @@ -164,14 +167,24 @@ class RecentActionsViewModel @Inject constructor( val parentNode = getNodeByHandle(bucket.parentHandle) val sharesType = getParentSharesType(parentNode) - - recentItemList.add(RecentActionItemType.Item( - bucket, - userName, - parentNode?.name ?: "", - sharesType, - currentUserIsOwner, - )) + if (!bucket.nodes[0].isNodeKeyDecrypted) { + recentItemList.add(RecentActionItemType.Item( + bucket, + userName, + parentNode?.name ?: "", + sharesType, + currentUserIsOwner, + areCredentialsVerified(bucket.userEmail), + )) + } else { + recentItemList.add(RecentActionItemType.Item( + bucket, + userName, + parentNode?.name ?: "", + sharesType, + currentUserIsOwner, + )) + } } return recentItemList diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt index 4c47f4ded64..724701cf1e8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt @@ -26,6 +26,7 @@ sealed class RecentActionItemType(val timestamp: Long) { val parentFolderName: String = "", val parentFolderSharesType: RecentActionsSharesType = RecentActionsSharesType.NONE, val currentUserIsOwner: Boolean = false, + val areCredentialsVerified: Boolean = false, ) : RecentActionItemType(timestamp = bucket.timestamp) /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt index 7e63350e1a3..56736674490 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt @@ -70,6 +70,8 @@ class RecentActionsViewModelTest { } private val monitorNodeUpdates = FakeMonitorUpdates() + private val areCredentialsVerified = mock() + private val node: TypedFileNode = mock { on { id }.thenReturn(NodeId(123)) } @@ -115,6 +117,7 @@ class RecentActionsViewModelTest { getParentMegaNode, monitorHideRecentActivity, monitorNodeUpdates, + areCredentialsVerified, ) } From c75559712fd72f92aedbb0e1c076537b4de3f804 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 31 Jan 2023 19:31:26 +0530 Subject: [PATCH 093/334] Failing test case fixed --- .../presentation/recentactions/RecentActionsViewModel.kt | 3 ++- .../recentactions/RecentActionsViewModelTest.kt | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index db4584343c7..721a445d736 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -167,6 +167,7 @@ class RecentActionsViewModel @Inject constructor( val parentNode = getNodeByHandle(bucket.parentHandle) val sharesType = getParentSharesType(parentNode) + val areUserCredentialsVerified = areCredentialsVerified(bucket.userEmail) if (!bucket.nodes[0].isNodeKeyDecrypted) { recentItemList.add(RecentActionItemType.Item( bucket, @@ -174,7 +175,7 @@ class RecentActionsViewModel @Inject constructor( parentNode?.name ?: "", sharesType, currentUserIsOwner, - areCredentialsVerified(bucket.userEmail), + areUserCredentialsVerified, )) } else { recentItemList.add(RecentActionItemType.Item( diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt index 56736674490..486e1afe3c2 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt @@ -70,10 +70,13 @@ class RecentActionsViewModelTest { } private val monitorNodeUpdates = FakeMonitorUpdates() - private val areCredentialsVerified = mock() + private val areCredentialsVerified = mock { + onBlocking { invoke(any()) }.thenReturn(false) + } private val node: TypedFileNode = mock { on { id }.thenReturn(NodeId(123)) + on { isNodeKeyDecrypted }.thenReturn(false) } private val megaRecentActionBucket = mock { @@ -300,6 +303,7 @@ class RecentActionsViewModelTest { val expected = "Cloud drive" val parentNode = mock { on { name }.thenReturn(expected) + on { isNodeKeyDecrypted }.thenReturn(false) } whenever(getRecentActions()).thenReturn(listOf(megaRecentActionBucket)) whenever(getNodeByHandle(any())).thenReturn(parentNode) @@ -372,7 +376,7 @@ class RecentActionsViewModelTest { on { isOutShare }.thenReturn(false) } whenever(getRecentActions()).thenReturn(listOf(megaRecentActionBucket)) - whenever(getNodeByHandle(any())).thenReturn(parentNode) + whenever(getNodeByHandle(1)).thenReturn(parentNode) whenever(isPendingShare(parentNode.handle)).thenReturn(false) whenever(getParentMegaNode(parentNode)).thenReturn(null) underTest.state.map { it.recentActionItems }.distinctUntilChanged() From 270df729102e92b6bf2945dcef092cede918a1a4 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 1 Feb 2023 09:47:08 +0530 Subject: [PATCH 094/334] Code comments resolved --- .../recentactions/RecentActionsAdapter.kt | 4 +-- .../recentactions/RecentActionsFragment.kt | 18 +----------- .../recentactions/RecentActionsViewModel.kt | 29 +++++++------------ .../model/RecentActionItemType.kt | 3 +- 4 files changed, 14 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index 5b7003d82a6..b6f5288338c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -72,8 +72,6 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter - if (!item.bucket.nodes[0].isNodeKeyDecrypted || - !megaApi.areCredentialsVerified(megaApi.getContact(item.bucket.nodes[0].id.longValue.toString())) - ) { + if (!item.isKeyVerified) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, item.bucket.userEmail) requireActivity().startActivity(this) @@ -191,20 +189,6 @@ class RecentActionsFragment : Fragment() { TopSnappedStickyLayoutManager(requireContext()) { recentActionItems } } - /** - * Function to get user email from the adapter data set only if it contains bucket otherwise returns blank - * - * @param recentActionItems List of [RecentActionItemType] which is provided to adapter - */ - private fun getUserEmail(recentActionItems: List): String { - for (itemType: RecentActionItemType in recentActionItems) { - if (itemType is RecentActionItemType.Item) { - return itemType.bucket.userEmail - } - } - return "" - } - /** * Display the recent actions activity. * Hide the activity if the setting to hide is enabled, and shows it if the diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index 721a445d736..f7e5a28403c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -167,25 +167,16 @@ class RecentActionsViewModel @Inject constructor( val parentNode = getNodeByHandle(bucket.parentHandle) val sharesType = getParentSharesType(parentNode) - val areUserCredentialsVerified = areCredentialsVerified(bucket.userEmail) - if (!bucket.nodes[0].isNodeKeyDecrypted) { - recentItemList.add(RecentActionItemType.Item( - bucket, - userName, - parentNode?.name ?: "", - sharesType, - currentUserIsOwner, - areUserCredentialsVerified, - )) - } else { - recentItemList.add(RecentActionItemType.Item( - bucket, - userName, - parentNode?.name ?: "", - sharesType, - currentUserIsOwner, - )) - } + val isNodeKeyDecrypted = + bucket.nodes[0].isNodeKeyDecrypted && areCredentialsVerified(bucket.userEmail) + recentItemList.add(RecentActionItemType.Item( + bucket, + userName, + parentNode?.name ?: "", + sharesType, + currentUserIsOwner, + isNodeKeyDecrypted, + )) } return recentItemList diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt index 724701cf1e8..c79d3b2cb5b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt @@ -19,6 +19,7 @@ sealed class RecentActionItemType(val timestamp: Long) { * @property parentFolderName the name of the parent folder containing the nodes * @property parentFolderSharesType the share type of the parent folder * @property currentUserIsOwner true if the current user is the owner of the recent actions + * @property isKeyVerified true if node.isNodeKeyDecrypted & areCredentialsVerified returns true */ class Item( val bucket: RecentActionBucket, @@ -26,7 +27,7 @@ sealed class RecentActionItemType(val timestamp: Long) { val parentFolderName: String = "", val parentFolderSharesType: RecentActionsSharesType = RecentActionsSharesType.NONE, val currentUserIsOwner: Boolean = false, - val areCredentialsVerified: Boolean = false, + val isKeyVerified: Boolean = false, ) : RecentActionItemType(timestamp = bucket.timestamp) /** From c90098522b6cf63cc0447739eaae37ebc2dafced Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 2 Feb 2023 17:56:31 +1300 Subject: [PATCH 095/334] Added secure flag switch on TourFragment --- .../mega/privacy/android/app/BaseActivity.kt | 1 - .../android/app/featuretoggle/AppFeatures.kt | 6 +++++ .../privacy/android/app/main/TourFragment.kt | 23 +++++++++++++++++-- .../assets/featuretoggle/feature_flags.json | 8 +++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt index b01ff13255c..10e01a9bd78 100644 --- a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt @@ -463,7 +463,6 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - megaApi.setSecureFlag(true) nameCollisionActivityContract = registerForActivityResult(NameCollisionActivityContract()) { result: String? -> if (result != null) { diff --git a/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt b/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt index b31c2289c0a..c134804cb88 100644 --- a/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt +++ b/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt @@ -20,6 +20,12 @@ enum class AppFeatures(override val description: String, private val defaultValu AndroidSync("Enable a synchronization between folders on local storage and folders on MEGA cloud", false), + + /** + * Sets the MegaApi::setSecureFlag + */ + SetSecureFlag("Sets the secure flag value for MegaApi", false), + /** * Indicates if the user is cryptographically secure */ diff --git a/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt b/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt index e869f9feb99..47b6ed4273b 100644 --- a/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt @@ -20,25 +20,35 @@ import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.viewpager.widget.ViewPager import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import mega.privacy.android.app.BaseActivity +import mega.privacy.android.app.MegaApplication import mega.privacy.android.app.MegaApplication.Companion.isLoggingOut import mega.privacy.android.app.R import mega.privacy.android.app.TourImageAdapter import mega.privacy.android.app.constants.IntentConstants import mega.privacy.android.app.databinding.DialogRecoveryKeyBinding import mega.privacy.android.app.databinding.FragmentTourBinding +import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.meeting.fragments.PasteMeetingLinkGuestDialogFragment import mega.privacy.android.app.presentation.login.LoginActivity import mega.privacy.android.app.utils.Constants import mega.privacy.android.app.utils.permission.PermissionUtils.hasPermissions +import mega.privacy.android.data.qualifier.MegaApi +import mega.privacy.android.domain.usecase.GetFeatureFlagValue +import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaApiJava import timber.log.Timber +import javax.inject.Inject /** * Tour Fragment. */ +@AndroidEntryPoint class TourFragment : Fragment() { private var _binding: FragmentTourBinding? = null @@ -47,6 +57,13 @@ class TourFragment : Fragment() { private lateinit var joinMeetingAsGuestLauncher: ActivityResultLauncher + @Inject + @MegaApi + lateinit var megaApi: MegaApiAndroid + + @Inject + lateinit var getFeatureFlagValue: GetFeatureFlagValue + private val selectedCircle by lazy { ContextCompat.getDrawable(requireContext(), R.drawable.selection_circle_page_adapter) } @@ -81,9 +98,7 @@ class TourFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupView() - arguments?.getString(EXTRA_RECOVERY_KEY_URL, null)?.let { recoveryKeyUrl -> Timber.d("Link to resetPass: $recoveryKeyUrl") showRecoveryKeyDialog(recoveryKeyUrl) @@ -139,6 +154,10 @@ class TourFragment : Fragment() { } }) } + + viewLifecycleOwner.lifecycleScope.launch { + megaApi.setSecureFlag(getFeatureFlagValue(AppFeatures.SetSecureFlag)) + } } /** diff --git a/app/src/qa/assets/featuretoggle/feature_flags.json b/app/src/qa/assets/featuretoggle/feature_flags.json index dd9c6fb6fe3..57ca03d12da 100644 --- a/app/src/qa/assets/featuretoggle/feature_flags.json +++ b/app/src/qa/assets/featuretoggle/feature_flags.json @@ -2,5 +2,13 @@ { "name": "PermanentLogging", "value": true + }, + { + "name": "MandatoryFingerprintVerification", + "value": true + }, + { + "name": "SetSecureFlag", + "value": true } ] \ No newline at end of file From 2d894d306d61679b59fbf75fa4737d7b3cc17f59 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 3 Feb 2023 17:22:01 +0530 Subject: [PATCH 096/334] Undecrypted file node UI bug fixed --- .../app/main/adapters/MegaNodeAdapter.java | 8 +++---- .../NodeOptionsBottomSheetDialogFragment.java | 24 ++++++++++++++----- .../recentactions/RecentActionsAdapter.kt | 15 +++++++++++- .../recentactions/RecentActionsFragment.kt | 24 ++++++++++++++++++- .../recentactions/RecentActionsViewModel.kt | 2 +- .../shares/incoming/IncomingSharesFragment.kt | 5 +--- .../assets/featuretoggle/feature_flags.json | 2 +- 7 files changed, 62 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 7240539d999..c8626a330e0 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1093,10 +1093,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if (isMandatoryFingerprintVerificationNeeded) { - if (!node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getContact(String.valueOf(node.getOwner())))) { - showUnverifiedNodeUi(holder, true); - } + if (isMandatoryFingerprintVerificationNeeded + && !unverifiedIncomingNodeHandles.isEmpty() + && unverifiedIncomingNodeHandles.contains(node.getHandle())) { + showUnverifiedNodeUi(holder, true); } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 1d455f51356..e34f2a07616 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -728,7 +728,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat ViewExtensionsKt.collectFlow(requireActivity(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE - && isIncomingNodeVerified()) { + && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedIncomingShares()); hideNodeActions(); } @@ -924,10 +924,6 @@ private boolean isNodeUnverified(List shareDataList) { return shareDataList.contains(node.getHandle()); } - private boolean isIncomingNodeVerified() { - return !node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getContact(String.valueOf(node.getOwner()))); - } - @SuppressLint("NonConstantResourceId") @Override public void onClick(View v) { @@ -1007,7 +1003,23 @@ public void onClick(View v) { break; case R.id.share_folder_option: - showShareFolderOptions(); + if(incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() + && + (!node.isNodeKeyDecrypted() || + !megaApi.areCredentialsVerified( + megaApi.getContact(String.valueOf(node.getOwner()))) + ) + ) { + megaApi.openShareDialog(node, new OptionalMegaRequestListenerInterface() { + @Override + public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { + super.onRequestFinish(api, request, error); + showShareFolderOptions(); + } + }); + } else { + showShareFolderOptions(); + } break; case R.id.clear_share_option: diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index b6f5288338c..023dc0beeeb 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -72,6 +72,9 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter + private lateinit var unverifiedOutgoingNodeHandles: HashSet + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecentActionViewHolder { val binding = ItemBucketBinding.inflate(LayoutInflater.from(parent.context), parent, false) @@ -191,7 +194,7 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter) { + unverifiedIncomingNodeHandles = HashSet() + unverifiedIncomingNodeHandles.addAll(handles) + } + + fun setUnverifiedOutgoingNodeHandles(handles: List) { + unverifiedOutgoingNodeHandles = HashSet() + unverifiedOutgoingNodeHandles.addAll(handles) + } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt index 164f4422097..b971efcdc97 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt @@ -35,6 +35,8 @@ import mega.privacy.android.app.main.PdfViewerActivity import mega.privacy.android.app.modalbottomsheet.NodeOptionsBottomSheetDialogFragment import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity import mega.privacy.android.app.presentation.recentactions.model.RecentActionItemType +import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel +import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel import mega.privacy.android.app.utils.Constants import mega.privacy.android.app.utils.FileUtil import mega.privacy.android.app.utils.MegaApiUtils @@ -77,6 +79,8 @@ class RecentActionsFragment : Fragment() { private lateinit var fastScroller: FastScroller private val viewModel: RecentActionsViewModel by activityViewModels() + private val incomingSharesViewModel: IncomingSharesViewModel by activityViewModels() + private val outGoingSharesViewModel: OutgoingSharesViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, @@ -122,6 +126,22 @@ class RecentActionsFragment : Fragment() { fastScroller = binding.fastscroll initAdapter() + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + incomingSharesViewModel.state.collect { + adapter.setUnverifiedIncomingNodeHandles(it.unVerifiedIncomingNodeHandles) + } + } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + outGoingSharesViewModel.state.collect { + adapter.setUnverifiedOutgoingNodeHandles(it.unVerifiedOutgoingNodeHandles) + } + } + } + } /** @@ -130,7 +150,9 @@ class RecentActionsFragment : Fragment() { private fun initAdapter() { adapter.setOnItemClickListener { item, position -> - if (!item.isKeyVerified) { + if (incomingSharesViewModel.state.value.unVerifiedIncomingNodeHandles.contains(item.bucket.nodes[0].id.longValue) + || outGoingSharesViewModel.state.value.unVerifiedOutgoingNodeHandles.contains(item.bucket.nodes[0].id.longValue) + ) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, item.bucket.userEmail) requireActivity().startActivity(this) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index f7e5a28403c..333d277e919 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -168,7 +168,7 @@ class RecentActionsViewModel @Inject constructor( val parentNode = getNodeByHandle(bucket.parentHandle) val sharesType = getParentSharesType(parentNode) val isNodeKeyDecrypted = - bucket.nodes[0].isNodeKeyDecrypted && areCredentialsVerified(bucket.userEmail) + !bucket.nodes[0].isNodeKeyDecrypted && !areCredentialsVerified(bucket.userEmail) recentItemList.add(RecentActionItemType.Item( bucket, userName, diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 9df3393bb35..5a69738d9c8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -97,10 +97,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { override fun itemClick(position: Int) { val actualPosition = position - 1 - - if (!state().nodes[actualPosition].isNodeKeyDecrypted || - !megaApi.areCredentialsVerified(megaApi.getContact(state().nodes[actualPosition].owner.toString())) - ) { + if (state().unVerifiedIncomingNodeHandles.contains(state().nodes[actualPosition].handle)) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, ContactUtil.getContactEmailDB(state().nodes[actualPosition].owner)) diff --git a/app/src/qa/assets/featuretoggle/feature_flags.json b/app/src/qa/assets/featuretoggle/feature_flags.json index 57ca03d12da..903fef89531 100644 --- a/app/src/qa/assets/featuretoggle/feature_flags.json +++ b/app/src/qa/assets/featuretoggle/feature_flags.json @@ -9,6 +9,6 @@ }, { "name": "SetSecureFlag", - "value": true + "value": false } ] \ No newline at end of file From be311d39685f6b0e39962a731c4af10e1ea8ff90 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 7 Feb 2023 23:40:45 +0530 Subject: [PATCH 097/334] Code review comments resolved --- .../privacy/android/app/di/GetNodeModule.kt | 4 +- .../app/di/featuretoggle/FeatureFlagModule.kt | 11 + .../android/app/main/ManagerActivity.java | 380 ++---------------- .../privacy/android/app/main/TourFragment.kt | 11 +- .../NodeOptionsBottomSheetDialogFragment.java | 1 - .../recentactions/RecentActionsViewModel.kt | 3 - .../presentation/search/SearchViewModel.kt | 2 +- .../incoming/IncomingSharesViewModel.kt | 4 +- .../outgoing/OutgoingSharesViewModel.kt | 4 +- .../outgoing/model/OutgoingSharesState.kt | 3 + .../android/app/di/TestGetNodeModule.kt | 4 +- .../RecentActionsViewModelTest.kt | 6 - .../incoming/IncomingSharesViewModelTest.kt | 2 +- 13 files changed, 64 insertions(+), 371 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt index 6736fcc4c15..0c723394994 100644 --- a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt @@ -8,11 +8,11 @@ import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.rx3.await import mega.privacy.android.app.domain.usecase.CheckNameCollision import mega.privacy.android.app.domain.usecase.CopyNode +import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.namecollision.usecase.CheckNameCollisionUseCase import mega.privacy.android.app.usecase.MoveNodeUseCase -import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares import mega.privacy.android.domain.usecase.SetSecureFlag @@ -92,7 +92,7 @@ abstract class GetNodeModule { */ @Provides fun provideGetUnVerifiedInComingShares(megaNodeRepository: MegaNodeRepository): GetUnverifiedIncomingShares = - GetUnverifiedIncomingShares(megaNodeRepository::getUnVerifiedInComingShares) + GetUnverifiedIncomingShares(megaNodeRepository::getUnverifiedIncomingShares) /** * Provides [GetUnverifiedOutgoingShares] implementation diff --git a/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt b/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt index f158d626f02..2e71d60bf55 100644 --- a/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt @@ -10,11 +10,13 @@ import dagger.multibindings.IntoMap import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.data.featuretoggle.file.FileFeatureFlagValueProvider import mega.privacy.android.data.qualifier.FeatureFlagPriorityKey +import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.domain.entity.Feature import mega.privacy.android.domain.featuretoggle.FeatureFlagValuePriority import mega.privacy.android.domain.featuretoggle.FeatureFlagValueProvider import mega.privacy.android.domain.usecase.DefaultGetFeatureFlagValue import mega.privacy.android.domain.usecase.GetFeatureFlagValue +import mega.privacy.android.domain.usecase.SetSecureFlag /** * Feature flag module @@ -68,5 +70,14 @@ abstract class FeatureFlagModule { fun provideFeatureFlagValueProvider(): @JvmSuppressWildcards FeatureFlagValueProvider = AppFeatures.Companion + /** + * Provides [SetSecureFlag] implementation + * + * @param filesRepository [FilesRepository] + * @return [SetSecureFlag] + */ + @Provides + fun provideSetSecureFlag(megaNodeRepository: MegaNodeRepository): SetSecureFlag = + SetSecureFlag(megaNodeRepository::setSecureFlag) } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 588ad7e78eb..00e0daf6bcc 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -355,9 +355,11 @@ import mega.privacy.android.app.objects.PasscodeManagement; import mega.privacy.android.app.presentation.clouddrive.FileBrowserFragment; import mega.privacy.android.app.presentation.clouddrive.FileBrowserViewModel; +import mega.privacy.android.app.presentation.fileinfo.FileInfoActivity; import mega.privacy.android.app.presentation.fingerprintauth.SecurityUpgradeDialogFragment; import mega.privacy.android.app.presentation.inbox.InboxFragment; import mega.privacy.android.app.presentation.inbox.InboxViewModel; +import mega.privacy.android.app.presentation.login.LoginActivity; import mega.privacy.android.app.presentation.manager.ManagerViewModel; import mega.privacy.android.app.presentation.manager.UnreadUserAlertsCheckType; import mega.privacy.android.app.presentation.manager.UserInfoViewModel; @@ -371,6 +373,7 @@ import mega.privacy.android.app.presentation.photos.albums.AlbumDynamicContentFragment; import mega.privacy.android.app.presentation.photos.mediadiscovery.MediaDiscoveryFragment; import mega.privacy.android.app.presentation.photos.timeline.photosfilter.PhotosFilterFragment; +import mega.privacy.android.app.presentation.qrcode.scan.ScanCodeFragment; import mega.privacy.android.app.presentation.rubbishbin.RubbishBinFragment; import mega.privacy.android.app.presentation.rubbishbin.RubbishBinViewModel; import mega.privacy.android.app.presentation.search.SearchFragment; @@ -435,6 +438,7 @@ import mega.privacy.android.domain.entity.contacts.ContactRequest; import mega.privacy.android.domain.entity.contacts.ContactRequestStatus; import mega.privacy.android.domain.entity.preference.ViewType; +import mega.privacy.android.domain.entity.user.UserCredentials; import mega.privacy.android.domain.qualifier.ApplicationScope; import nz.mega.documentscanner.DocumentScannerActivity; import nz.mega.sdk.MegaAccountDetails; @@ -613,14 +617,8 @@ public class ManagerActivity extends TransfersManagementActivity MegaNode parentNodeManager; public DrawerLayout drawerLayout; - ArrayList contacts = new ArrayList<>(); - ArrayList visibleContacts = new ArrayList<>(); public boolean openFolderRefresh = false; - - public boolean openSettingsStartScreen; - public boolean openSettingsStorage = false; - public boolean openSettingsQR = false; boolean newAccount = false; public boolean newCreationAccount; @@ -630,8 +628,6 @@ public class ManagerActivity extends TransfersManagementActivity private boolean isStorageStatusDialogShown = false; - private boolean isTransferOverQuotaWarningShown; - private AlertDialog transferOverQuotaWarning; private AlertDialog confirmationTransfersDialog; private AlertDialog reconnectDialog; @@ -653,56 +649,6 @@ public class ManagerActivity extends TransfersManagementActivity private boolean isInAlbumContent; public boolean fromAlbumContent = false; - public enum FragmentTag { - CLOUD_DRIVE, HOMEPAGE, PHOTOS, INBOX, INCOMING_SHARES, OUTGOING_SHARES, SEARCH, TRANSFERS, COMPLETED_TRANSFERS, - RECENT_CHAT, RUBBISH_BIN, NOTIFICATIONS, TURN_ON_NOTIFICATIONS, PERMISSIONS, SMS_VERIFICATION, - LINKS, MEDIA_DISCOVERY, ALBUM_CONTENT, PHOTOS_FILTER; - - public String getTag() { - switch (this) { - case CLOUD_DRIVE: - return "fileBrowserFragment"; - case HOMEPAGE: - return "homepageFragment"; - case RUBBISH_BIN: - return "rubbishBinFragment"; - case PHOTOS: - return "photosFragment"; - case INBOX: - return "inboxFragment"; - case INCOMING_SHARES: - return "incomingSharesFragment"; - case OUTGOING_SHARES: - return "outgoingSharesFragment"; - case SEARCH: - return "searchFragment"; - case TRANSFERS: - return "android:switcher:" + R.id.transfers_tabs_pager + ":" + 0; - case COMPLETED_TRANSFERS: - return "android:switcher:" + R.id.transfers_tabs_pager + ":" + 1; - case RECENT_CHAT: - return "chatTabsFragment"; - case NOTIFICATIONS: - return "notificationsFragment"; - case TURN_ON_NOTIFICATIONS: - return "turnOnNotificationsFragment"; - case PERMISSIONS: - return "permissionsFragment"; - case SMS_VERIFICATION: - return "smsVerificationFragment"; - case LINKS: - return "linksFragment"; - case MEDIA_DISCOVERY: - return "mediaDiscoveryFragment"; - case ALBUM_CONTENT: - return "fragmentAlbumContent"; - case PHOTOS_FILTER: - return "fragmentPhotosFilter"; - } - return null; - } - } - public boolean turnOnNotifications = false; private DrawerItem drawerItem; @@ -736,8 +682,6 @@ public String getTag() { private RelativeLayout callInProgressLayout; private Chronometer callInProgressChrono; private TextView callInProgressText; - private LinearLayout microOffLayout; - private LinearLayout videoOnLayout; boolean firstTimeAfterInstallation = true; SearchView searchView; @@ -754,11 +698,6 @@ public String getTag() { private HomepageScreen mHomepageScreen = HomepageScreen.HOMEPAGE; - private enum HomepageScreen { - HOMEPAGE, IMAGES, FAVOURITES, DOCUMENTS, AUDIO, VIDEO, - FULLSCREEN_OFFLINE, OFFLINE_FILE_INFO, RECENT_BUCKET - } - public boolean isList = true; private String pathNavigationOffline; @@ -777,7 +716,6 @@ private enum HomepageScreen { private Fragment albumContentFragment; private PhotosFilterFragment photosFilterFragment; private ChatTabsFragment chatTabsFragment; - private NotificationsFragment notificationsFragment; private TurnOnNotificationsFragment turnOnNotificationsFragment; private PermissionsFragment permissionsFragment; private SMSVerificationFragment smsVerificationFragment; @@ -793,17 +731,12 @@ private enum HomepageScreen { private AlertDialog permissionsDialog; private AlertDialog presenceStatusDialog; - private AlertDialog alertNotPermissionsUpload; - private AlertDialog clearRubbishBinDialog; - private AlertDialog insertPassDialog; - private AlertDialog changeUserAttributeDialog; private AlertDialog alertDialogStorageStatus; private AlertDialog alertDialogSMSVerification; private AlertDialog newTextFileDialog; private AlertDialog newFolderDialog; private MenuItem searchMenuItem; - private MenuItem enableSelectMenuItem; private MenuItem doNotDisturbMenuItem; private MenuItem clearRubbishBinMenuitem; private MenuItem cancelAllTransfersMenuItem; @@ -811,7 +744,6 @@ private enum HomepageScreen { private MenuItem pauseTransfersMenuIcon; private MenuItem retryTransfers; private MenuItem clearCompletedTransfers; - private MenuItem scanQRcodeMenuItem; private MenuItem returnCallMenuItem; private MenuItem openLinkMenuItem; private Chronometer chronometerMenuItem; @@ -823,8 +755,6 @@ private enum HomepageScreen { Button enable2FAButton; Button skip2FAButton; - private boolean is2FAEnabled = false; - public boolean comesFromNotifications = false; public int comesFromNotificationsLevel = 0; public long comesFromNotificationHandle = INVALID_VALUE; @@ -847,6 +777,7 @@ private enum HomepageScreen { int bottomNavigationCurrentItem = -1; View chatBadge; View callBadge; + BottomNavigationMenuView menuView; private boolean joiningToChatLink; private String linkJoinToChatLink; @@ -919,8 +850,6 @@ public void onChanged(Boolean aBoolean) { private final ArrayList fabs = new ArrayList<>(); // end for Meeting - // Backup warning dialog - private AlertDialog backupWarningDialog; private ArrayList backupHandleList; private int backupDialogType = BACKUP_DIALOG_SHOW_NONE; private Long backupNodeHandle; @@ -1209,35 +1138,6 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis break; } - case REQUEST_CAMERA_UPLOAD: - case REQUEST_CAMERA_ON_OFF: - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - checkIfShouldShowBusinessCUAlert(); - } else { - stopCameraUploadSyncHeartbeatWorkers(this); - showSnackbar(SNACKBAR_TYPE, getString(R.string.on_refuse_storage_permission), INVALID_HANDLE); - } - - break; - - case REQUEST_CAMERA_ON_OFF_FIRST_TIME: - if (permissions.length == 0) { - return; - } - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - checkIfShouldShowBusinessCUAlert(); - } else { - if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[0])) { - if (getPhotosFragment() != null) { - photosFragment.onStoragePermissionRefused(); - } - } else { - showSnackbar(SNACKBAR_TYPE, getString(R.string.on_refuse_storage_permission), INVALID_HANDLE); - } - } - - break; - case PERMISSIONS_FRAGMENT: { if (getPermissionsFragment() != null) { permissionsFragment.setNextPermission(); @@ -1321,7 +1221,6 @@ public void onSaveInstanceState(Bundle outState) { outState.putBoolean(BUSINESS_CU_ALERT_SHOWN, isBusinessCUAlertShown); } - outState.putBoolean(TRANSFER_OVER_QUOTA_SHOWN, isTransferOverQuotaWarningShown); outState.putInt(TYPE_CALL_PERMISSION, typesCameraPermission); outState.putBoolean(JOINING_CHAT_LINK, joiningToChatLink); outState.putString(LINK_JOINING_CHAT_LINK, linkJoinToChatLink); @@ -1495,7 +1394,6 @@ protected void onCreate(Bundle savedInstanceState) { openLinkDialogIsShown = savedInstanceState.getBoolean(OPEN_LINK_DIALOG_SHOWN, false); isBusinessGraceAlertShown = savedInstanceState.getBoolean(BUSINESS_GRACE_ALERT_SHOWN, false); isBusinessCUAlertShown = savedInstanceState.getBoolean(BUSINESS_CU_ALERT_SHOWN, false); - isTransferOverQuotaWarningShown = savedInstanceState.getBoolean(TRANSFER_OVER_QUOTA_SHOWN, false); typesCameraPermission = savedInstanceState.getInt(TYPE_CALL_PERMISSION, INVALID_TYPE_PERMISSIONS); joiningToChatLink = savedInstanceState.getBoolean(JOINING_CHAT_LINK, false); linkJoinToChatLink = savedInstanceState.getString(LINK_JOINING_CHAT_LINK); @@ -1557,11 +1455,6 @@ protected void onCreate(Bundle savedInstanceState) { LiveEventBus.get(EVENT_REFRESH_PHONE_NUMBER, Boolean.class) .observeForever(refreshAddPhoneNumberButtonObserver); - LiveEventBus.get(EVENT_TRANSFER_OVER_QUOTA, Boolean.class).observe(this, update -> { - updateTransfersWidget(TransferType.NONE); - showTransfersTransferOverQuotaWarning(); - }); - LiveEventBus.get(EVENT_FAILED_TRANSFERS, Boolean.class).observe(this, failed -> { if (drawerItem == DrawerItem.TRANSFERS && getTabItemTransfers() == TransfersTab.COMPLETED_TAB) { retryTransfers.setVisible(failed); @@ -1802,7 +1695,7 @@ public boolean onPreDraw() { badgeDrawable = new BadgeDrawerArrowDrawable(managerActivity, R.color.red_600_red_300, R.color.white_dark_grey, R.color.white_dark_grey); - BottomNavigationMenuView menuView = (BottomNavigationMenuView) bNV.getChildAt(0); + menuView = (BottomNavigationMenuView) bNV.getChildAt(0); // Navi button Chat BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(3); chatBadge = LayoutInflater.from(this).inflate(R.layout.bottom_chat_badge, menuView, false); @@ -1961,8 +1854,6 @@ public void onPageScrollStateChanged(int state) { callInProgressLayout.setOnClickListener(this); callInProgressChrono = findViewById(R.id.call_in_progress_chrono); callInProgressText = findViewById(R.id.call_in_progress_text); - microOffLayout = findViewById(R.id.micro_off_layout); - videoOnLayout = findViewById(R.id.video_on_layout); callInProgressLayout.setVisibility(View.GONE); if (mElevationCause > 0) { @@ -2262,7 +2153,7 @@ public void onPageScrollStateChanged(int state) { selectDrawerItemPending = false; } else if (fragmentHandle == megaApi.getRubbishNode().getHandle()) { drawerItem = DrawerItem.RUBBISH_BIN; - viewModel.setRubbishBinParentHandle(handleIntent); + rubbishBinViewModel.setRubbishBinHandle(handleIntent); selectDrawerItem(drawerItem); selectDrawerItemPending = false; } else if (fragmentHandle == megaApi.getInboxNode().getHandle()) { @@ -2565,10 +2456,6 @@ public void onPageScrollStateChanged(int state) { } } - if (drawerItem == DrawerItem.TRANSFERS && isTransferOverQuotaWarningShown) { - showTransfersTransferOverQuotaWarning(); - } - PsaManager.INSTANCE.startChecking(); if (savedInstanceState != null && savedInstanceState.getBoolean(IS_NEW_TEXT_FILE_SHOWN, false)) { @@ -2598,21 +2485,6 @@ public void onPageScrollStateChanged(int state) { } else { Timber.d("Backup warning dialog is not show"); } - ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { - if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { - addUnverifiedIncomingCountBadge(incomingSharesState.getUnverifiedIncomingShares().size()); - } - return Unit.INSTANCE; - }); - - ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { - if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { - addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnverifiedOutgoingShares().size()); - } - return Unit.INSTANCE; - }); - - setPendingActionsBadge(menuView); } /** @@ -2661,6 +2533,31 @@ private void collectFlows() { nVEmail.setText(state.getEmail()); return Unit.INSTANCE; }); + + ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { + if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { + addUnverifiedIncomingCountBadge(incomingSharesState.getUnverifiedIncomingShares().size()); + } + return Unit.INSTANCE; + }); + + ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { + if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { + addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnverifiedOutgoingShares().size()); + } + return Unit.INSTANCE; + }); + + ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { + if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { + BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); + View pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_pending_actions_badge, menuView, false); + sharedItemsView.addView(pendingActionsBadge); + TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.pending_actions_badge_text); + tvPendingActionsCount.setText(String.valueOf(managerState.getPendingActionsCount())); + } + return Unit.INSTANCE; + }); } /** @@ -2776,32 +2673,6 @@ private void showBusinessGraceAlert() { isBusinessGraceAlertShown = true; } - /** - * If the account is business and not a master user, it shows a warning. - * Otherwise proceeds to enable CU. - */ - public void checkIfShouldShowBusinessCUAlert() { - if (isBusinessAccount() && !megaApi.isMasterBusinessAccount()) { - showBusinessCUAlert(); - } else { - enableCUClicked(); - } - } - - - /** - * Proceeds to enable CU action. - */ - private void enableCUClicked() { - if (getPhotosFragment() != null) { - if (photosFragment.isEnablePhotosViewShown()) { - photosFragment.enableCameraUpload(); - } else { - photosFragment.enableCameraUploadClick(); - } - } - } - /** * Shows a warning to business users about the risks of enabling CU. */ @@ -3174,7 +3045,7 @@ void actionOpenFolder(long handleIntent) { default: if (megaApi.isInRubbish(parentIntentN)) { - viewModel.setRubbishBinParentHandle(handleIntent); + rubbishBinViewModel.setRubbishBinHandle(handleIntent); drawerItem = DrawerItem.RUBBISH_BIN; } else if (megaApi.isInInbox(parentIntentN)) { inboxViewModel.updateInboxHandle(handleIntent); @@ -3479,7 +3350,6 @@ protected void onPostResume() { } case NOTIFICATIONS: break; - } case HOMEPAGE: default: setBottomNavigationMenuItemChecked(HOME_BNV); @@ -3590,8 +3460,6 @@ protected void onDestroy() { LiveEventBus.get(EVENT_FINISH_ACTIVITY, Boolean.class).removeObserver(finishObserver); LiveEventBus.get(EVENT_FAB_CHANGE, Boolean.class).removeObserver(fabChangeObserver); - destroyPayments(); - cancelSearch(); if (reconnectDialog != null) { reconnectDialog.cancel(); @@ -3806,12 +3674,12 @@ public void setToolbarTitle() { } case RUBBISH_BIN: { aB.setSubtitle(null); - MegaNode node = megaApi.getNodeByHandle(viewModel.getState().getValue().getRubbishBinParentHandle()); + MegaNode node = megaApi.getNodeByHandle(rubbishBinState(ManagerActivity.this).getRubbishBinHandle()); MegaNode rubbishNode = megaApi.getRubbishNode(); if (rubbishNode == null) { - viewModel.setRubbishBinParentHandle(INVALID_HANDLE); + rubbishBinViewModel.setRubbishBinHandle(INVALID_HANDLE); viewModel.setIsFirstNavigationLevel(true); - } else if (viewModel.getState().getValue().getRubbishBinParentHandle() == INVALID_HANDLE || node == null || node.getHandle() == rubbishNode.getHandle()) { + } else if (rubbishBinState(ManagerActivity.this).getRubbishBinHandle() == INVALID_HANDLE || node == null || node.getHandle() == rubbishNode.getHandle()) { aB.setTitle(StringResourcesUtils.getString(R.string.section_rubbish_bin)); viewModel.setIsFirstNavigationLevel(true); } else { @@ -4566,7 +4434,6 @@ private void resetCUFragment() { cuViewTypes.setVisibility(View.GONE); if (getPhotosFragment() != null) { - photosFragment.setDefaultView(); showBottomView(); } } @@ -4696,7 +4563,6 @@ public void selectDrawerItem(DrawerItem item) { supportInvalidateOptionsMenu(); showFabButton(); showHideBottomNavigationView(false); - refreshCUNodes(); if (!comesFromNotifications) { bottomNavigationCurrentItem = PHOTOS_BNV; } @@ -4771,14 +4637,6 @@ public void selectDrawerItem(DrawerItem item) { } case CHAT: { Timber.d("Chat selected"); - if (megaApi != null) { - contacts = megaApi.getContacts(); - for (int i = 0; i < contacts.size(); i++) { - if (contacts.get(i).getVisibility() == MegaUser.VISIBILITY_VISIBLE) { - visibleContacts.add(contacts.get(i)); - } - } - } selectDrawerItemChat(); supportInvalidateOptionsMenu(); showHideBottomNavigationView(false); @@ -4963,12 +4821,6 @@ public void checkScrollElevation() { } break; } - case PHOTOS: { - if (getPhotosFragment() != null) { - photosFragment.checkScroll(); - } - break; - } case INBOX: { inboxFragment = (InboxFragment) getSupportFragmentManager().findFragmentByTag(FragmentTag.INBOX.getTag()); if (inboxFragment != null) { @@ -5291,7 +5143,7 @@ public boolean onQueryTextChange(String newText) { searchViewModel.setSearchQuery(newText); searchViewModel.performSearch( fileBrowserState(ManagerActivity.this).getFileBrowserHandle(), - viewModel.getState().getValue().getRubbishBinParentHandle(), + rubbishBinState(ManagerActivity.this).getRubbishBinHandle(), inboxState(ManagerActivity.this).getInboxHandle(), incomingSharesState(ManagerActivity.this).getIncomingHandle(), outgoingSharesState(ManagerActivity.this).getOutgoingHandle(), @@ -5312,7 +5164,6 @@ public boolean onQueryTextChange(String newText) { retryTransfers = menu.findItem(R.id.action_menu_retry_transfers); playTransfersMenuIcon = menu.findItem(R.id.action_play); pauseTransfersMenuIcon = menu.findItem(R.id.action_pause); - scanQRcodeMenuItem = menu.findItem(R.id.action_scan_qr); returnCallMenuItem = menu.findItem(R.id.action_return_call); RelativeLayout rootView = (RelativeLayout) returnCallMenuItem.getActionView(); layoutCallMenuItem = rootView.findViewById(R.id.layout_menu_call); @@ -6222,7 +6073,7 @@ private void proceedWithRestoration(List nodes) { .subscribe((result, throwable) -> { if (throwable == null) { boolean notValidView = result.isSingleAction() && result.isSuccess() - && viewModel.getState().getValue().getRubbishBinParentHandle() == nodes.get(0).getHandle(); + && rubbishBinState(ManagerActivity.this).getRubbishBinHandle() == nodes.get(0).getHandle(); showRestorationOrRemovalResult(notValidView, result.getResultText()); } else if (throwable instanceof ForeignNodeException) { @@ -6241,7 +6092,6 @@ private void showRestorationOrRemovalResult(boolean notValidView, String message if (notValidView) { rubbishBinViewModel.setRubbishBinHandle(INVALID_HANDLE); setToolbarTitle(); - refreshRubbishBin(); } dismissAlertDialogIfExists(statusDialog); @@ -7329,12 +7179,6 @@ public void cameraUploadsClicked() { selectDrawerItem(drawerItem); } - public void skipInitialCUSetup() { - viewModel.setIsFirstLogin(false); - drawerItem = getStartDrawerItem(); - selectDrawerItem(drawerItem); - } - /** * Refresh the UI of the Photos feature */ @@ -7351,27 +7195,6 @@ public void refreshPhotosFragment() { } } - /** - * Checks if should update some cu view visibility. - * - * @param visibility New requested visibility update. - * @return True if should apply the visibility update, false otherwise. - */ - private boolean rightCUVisibilityChange(int visibility) { - return drawerItem == DrawerItem.PHOTOS || visibility == View.GONE; - } - - /** - * Updates cuViewTypes view visibility. - * - * @param visibility New visibility value to set. - */ - public void updateCUViewTypes(int visibility) { - if (rightCUVisibilityChange(visibility)) { - cuViewTypes.setVisibility(visibility); - } - } - /** * Shows the bottom sheet to manage a completed transfer. * @@ -7624,8 +7447,6 @@ public void refreshCloudDrive() { if (comesFromNotificationChildNodeHandleList == null) { fileBrowserFragment.hideMultipleSelect(); } - fileBrowserFragment.setNodes(nodes); - fileBrowserFragment.getRecyclerView().invalidate(); } } @@ -7659,12 +7480,6 @@ public void refreshOthersOrder() { refreshSearch(); } - public void refreshCUNodes() { - if (getPhotosFragment() != null) { - photosFragment.loadPhotos(); - } - } - public void setFirstNavigationLevel(boolean firstNavigationLevel) { Timber.d("Set value to: %s", firstNavigationLevel); viewModel.setIsFirstNavigationLevel(firstNavigationLevel); @@ -9332,8 +9147,6 @@ public void onRequestFinish(MegaApiJava api, MegaRequest request, MegaError e) { Timber.d("Attribute USER_ATTR_GEOLOCATION disabled"); MegaApplication.setEnabledGeoLocation(false); } - } else if (request.getParamType() == MegaApiJava.USER_ATTR_DISABLE_VERSIONS) { - MegaApplication.setDisableFileVersions(request.getFlag()); } } else if (request.getType() == MegaRequest.TYPE_GET_CANCEL_LINK) { Timber.d("TYPE_GET_CANCEL_LINK"); @@ -9566,19 +9379,6 @@ public void onRequestFinish(MegaApiJava api, MegaRequest request, MegaError e) { } } - /** - * Updates own firstName/lastName and fullName data in UI and DB. - * - * @param firstName True if the update makes reference to the firstName, false it to the lastName. - * @param newName New firstName/lastName text. - * @param e MegaError of the request. - */ - private void updateMyData(boolean firstName, String newName, MegaError e) { - myAccountInfo.updateMyData(firstName, newName, e); - updateUserNameNavigationView(myAccountInfo.getFullName()); - LiveEventBus.get(EVENT_USER_NAME_UPDATED, Boolean.class).post(true); - } - @Override public void onRequestTemporaryError(MegaApiJava api, MegaRequest request, MegaError e) { @@ -9719,10 +9519,6 @@ public void openLocation(long nodeHandle, long[] childNodeHandleList) { public void updateUserAlerts(List userAlerts) { viewModel.checkNumUnreadUserAlerts(UnreadUserAlertsCheckType.NOTIFICATIONS_TITLE_AND_TOOLBAR_ICON); - notificationsFragment = (NotificationsFragment) getSupportFragmentManager().findFragmentByTag(FragmentTag.NOTIFICATIONS.getTag()); - if (notificationsFragment != null && userAlerts != null) { - notificationsFragment.updateNotifications(userAlerts); - } } public void updateMyEmail(String email) { @@ -10281,7 +10077,6 @@ public AndroidCompletedTransfer getSelectedTransfer() { } public MegaNode getSelectedNode() { - callOpenShareDialog(); return selectedNode; } @@ -10514,17 +10309,6 @@ public void setChatBadge() { } } - private void callOpenShareDialog() { - if (searchViewModel.getState().getValue().isMandatoryFingerPrintVerificationRequired()) { - megaApi.openShareDialog(selectedNode, new OptionalMegaRequestListenerInterface() { - @Override - public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { - super.onRequestFinish(api, request, error); - } - }); - } - } - public void setPendingActionsBadge(BottomNavigationMenuView menuView) { ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { @@ -10574,31 +10358,6 @@ public void refreshMenu() { supportInvalidateOptionsMenu(); } - public boolean is2FAEnabled() { - return is2FAEnabled; - } - - /** - * Sets or removes the layout behaviour to hide the bottom view when scrolling. - * - * @param enable True if should set the behaviour, false if should remove it. - */ - public void enableHideBottomViewOnScroll(boolean enable) { - LinearLayout layout = findViewById(R.id.container_bottom); - if (layout == null || isInImagesPage()) { - return; - } - - final CoordinatorLayout.LayoutParams fParams - = new CoordinatorLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - fParams.setMargins(0, 0, 0, enable ? 0 : getResources().getDimensionPixelSize(R.dimen.bottom_navigation_view_height)); - fragmentLayout.setLayoutParams(fParams); - - CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) layout.getLayoutParams(); - params.setBehavior(enable ? new CustomHideBottomViewOnScrollBehaviour() : null); - layout.setLayoutParams(params); - } - /** * Shows all the content of bottom view. */ @@ -10613,33 +10372,6 @@ public void showBottomView() { .start(); } - /** - * Shows or hides the bottom view and animates the transition. - * - * @param hide True if should hide it, false if should show it. - */ - public void animateBottomView(boolean hide) { - LinearLayout bottomView = findViewById(R.id.container_bottom); - if (bottomView == null || fragmentLayout == null) { - return; - } - - CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) fragmentLayout.getLayoutParams(); - - if (hide && bottomView.getVisibility() == View.VISIBLE) { - bottomView.animate().translationY(bottomView.getHeight()).setDuration(ANIMATION_DURATION) - .withStartAction(() -> params.bottomMargin = 0) - .withEndAction(() -> bottomView.setVisibility(View.GONE)).start(); - } else if (!hide && bottomView.getVisibility() == View.GONE) { - int bottomMargin = getResources().getDimensionPixelSize(R.dimen.bottom_navigation_view_height); - - bottomView.animate().translationY(0).setDuration(ANIMATION_DURATION) - .withStartAction(() -> bottomView.setVisibility(View.VISIBLE)) - .withEndAction(() -> params.bottomMargin = bottomMargin) - .start(); - } - } - public void showHideBottomNavigationView(boolean hide) { if (bNV == null) return; @@ -10955,29 +10687,6 @@ public void viewNodeInFolder(MegaNode node) { } } - /** - * Shows a "transfer over quota" warning. - */ - public void showTransfersTransferOverQuotaWarning() { - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); - int messageResource = R.string.warning_transfer_over_quota; - - transferOverQuotaWarning = builder.setTitle(R.string.label_transfer_over_quota) - .setMessage(getString(messageResource, getHumanizedTime(megaApi.getBandwidthOverquotaDelay()))) - .setPositiveButton(R.string.my_account_upgrade_pro, (dialog, which) -> { - navigateToUpgradeAccount(); - }) - .setNegativeButton(R.string.general_dismiss, null) - .setCancelable(false) - .setOnDismissListener(dialog -> isTransferOverQuotaWarningShown = false) - .create(); - - transferOverQuotaWarning.setCanceledOnTouchOutside(false); - TimeUtils.createAndShowCountDownTimer(messageResource, transferOverQuotaWarning); - transferOverQuotaWarning.show(); - isTransferOverQuotaWarningShown = true; - } - /** * Updates the position of the transfers widget. * @@ -11090,10 +10799,6 @@ private PermissionsFragment getPermissionsFragment() { return permissionsFragment = (PermissionsFragment) getSupportFragmentManager().findFragmentByTag(FragmentTag.PERMISSIONS.getTag()); } - public Fragment getMDFragment() { - return mediaDiscoveryFragment; - } - public Fragment getAlbumContentFragment() { return albumContentFragment; } @@ -11245,15 +10950,6 @@ public boolean isInPhotosPage() { return drawerItem == DrawerItem.PHOTOS; } - /** - * Checks if the current screen is Media discovery page. - * - * @return True if the current screen is Media discovery page, false otherwise. - */ - public boolean isInMDPage() { - return drawerItem == DrawerItem.CLOUD_DRIVE && isInMDMode; - } - /** * Create the instance of FileBackupManager */ diff --git a/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt b/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt index 47b6ed4273b..0892c278113 100644 --- a/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt @@ -26,7 +26,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import mega.privacy.android.app.BaseActivity -import mega.privacy.android.app.MegaApplication import mega.privacy.android.app.MegaApplication.Companion.isLoggingOut import mega.privacy.android.app.R import mega.privacy.android.app.TourImageAdapter @@ -38,9 +37,8 @@ import mega.privacy.android.app.meeting.fragments.PasteMeetingLinkGuestDialogFra import mega.privacy.android.app.presentation.login.LoginActivity import mega.privacy.android.app.utils.Constants import mega.privacy.android.app.utils.permission.PermissionUtils.hasPermissions -import mega.privacy.android.data.qualifier.MegaApi import mega.privacy.android.domain.usecase.GetFeatureFlagValue -import nz.mega.sdk.MegaApiAndroid +import mega.privacy.android.domain.usecase.SetSecureFlag import nz.mega.sdk.MegaApiJava import timber.log.Timber import javax.inject.Inject @@ -58,11 +56,10 @@ class TourFragment : Fragment() { private lateinit var joinMeetingAsGuestLauncher: ActivityResultLauncher @Inject - @MegaApi - lateinit var megaApi: MegaApiAndroid + lateinit var getFeatureFlagValue: GetFeatureFlagValue @Inject - lateinit var getFeatureFlagValue: GetFeatureFlagValue + lateinit var setSecureFlag: SetSecureFlag private val selectedCircle by lazy { ContextCompat.getDrawable(requireContext(), R.drawable.selection_circle_page_adapter) @@ -156,7 +153,7 @@ class TourFragment : Fragment() { } viewLifecycleOwner.lifecycleScope.launch { - megaApi.setSecureFlag(getFeatureFlagValue(AppFeatures.SetSecureFlag)) + setSecureFlag(getFeatureFlagValue(AppFeatures.SetSecureFlag)) } } diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index e34f2a07616..3273994fd1e 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -181,7 +181,6 @@ public class NodeOptionsBottomSheetDialogFragment extends BaseBottomSheetDialogF private IncomingSharesViewModel incomingSharesViewModel; private OutgoingSharesViewModel outgoingSharesViewModel; - private Set unverifiedHandles = new HashSet<>(); public NodeOptionsBottomSheetDialogFragment(int mode) { if (mode >= DEFAULT_MODE && mode <= FAVOURITES_MODE) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index 333d277e919..65f371f02d3 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -18,7 +18,6 @@ import mega.privacy.android.app.presentation.recentactions.model.RecentActionsSh import mega.privacy.android.app.presentation.recentactions.model.RecentActionsState import mega.privacy.android.domain.entity.RecentActionBucket import mega.privacy.android.domain.entity.contacts.ContactItem -import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -36,7 +35,6 @@ import javax.inject.Inject * @param getVisibleContacts * @param setHideRecentActivity * @param monitorNodeUpdates - * @param areCredentialsVerified */ @HiltViewModel class RecentActionsViewModel @Inject constructor( @@ -49,7 +47,6 @@ class RecentActionsViewModel @Inject constructor( private val getParentMegaNode: GetParentMegaNode, monitorHideRecentActivity: MonitorHideRecentActivity, monitorNodeUpdates: MonitorNodeUpdates, - val areCredentialsVerified: AreCredentialsVerified, ) : ViewModel() { private var _buckets = listOf() diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt index b8c7b5cea02..b991dcf40cb 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt @@ -334,7 +334,7 @@ class SearchViewModel @Inject constructor( /** * Gets the feature flag value & updates state */ - fun isMandatoryFingerprintRequired() { + private fun isMandatoryFingerprintRequired() { viewModelScope.launch { _state.update { it.copy(isMandatoryFingerPrintVerificationRequired = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index a458941ff23..04c3aaafaf8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -84,9 +84,7 @@ class IncomingSharesViewModel @Inject constructor( } val handles = unverifiedIncomingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .mapNotNull { shareData -> - getNodeByHandle(shareData.nodeHandle)?.handle - } + .map { shareData -> shareData.nodeHandle } _state.update { it.copy(nodes = unverifiedIncomingNodes, unverifiedIncomingShares = unverifiedIncomingShares, diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 476ae514328..d6703cd5faf 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -64,9 +64,7 @@ class OutgoingSharesViewModel @Inject constructor( val unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder) val handles = unverifiedOutgoingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .mapNotNull { shareData -> - getNodeByHandle(shareData.nodeHandle)?.handle - } + .map { shareData -> shareData.nodeHandle } _state.update { it.copy(unverifiedOutgoingShares = unverifiedOutgoingShares, unVerifiedOutgoingNodeHandles = handles) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index b326a7e9987..fde4487f23b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -2,6 +2,7 @@ package mega.privacy.android.app.presentation.shares.outgoing.model import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder +import mega.privacy.android.domain.usecase.AreCredentialsVerified import nz.mega.sdk.MegaNode /** @@ -17,6 +18,7 @@ import nz.mega.sdk.MegaNode * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles + * @param areUserCredentialsVerified Boolean value to read if user credentials are verified */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -29,6 +31,7 @@ data class OutgoingSharesState( val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodeHandles: List = emptyList(), + val areUserCredentialsVerified: Boolean = false, ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt index 26fe7ed5176..29fc17817a7 100644 --- a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt +++ b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt @@ -39,12 +39,12 @@ object TestGetNodeModule { } @Provides - fun provideGetUnVerifiedInComingShares() = mock() { + fun provideGetUnverifiedIncomingShares() = mock() { onBlocking { invoke(any()) }.thenReturn(emptyList()) } @Provides - fun provideGetUnverifiedOutGoingShares() = mock() { + fun provideGetUnverifiedOutgoingShares() = mock() { onBlocking { invoke(any()) }.thenReturn(emptyList()) } } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt index 486e1afe3c2..e07fe51f2ff 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt @@ -24,7 +24,6 @@ import mega.privacy.android.domain.entity.contacts.ContactItem import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.entity.node.TypedFileNode -import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -70,10 +69,6 @@ class RecentActionsViewModelTest { } private val monitorNodeUpdates = FakeMonitorUpdates() - private val areCredentialsVerified = mock { - onBlocking { invoke(any()) }.thenReturn(false) - } - private val node: TypedFileNode = mock { on { id }.thenReturn(NodeId(123)) on { isNodeKeyDecrypted }.thenReturn(false) @@ -120,7 +115,6 @@ class RecentActionsViewModelTest { getParentMegaNode, monitorHideRecentActivity, monitorNodeUpdates, - areCredentialsVerified, ) } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 6baf6512a1e..d423fda0313 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -528,7 +528,7 @@ class IncomingSharesViewModelTest { } @Test - fun `test that unverified outgoing shares are returned`() = runTest { + fun `test that unverified incoming shares are returned`() = runTest { val node1 = mock() whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() From 05da48c02904ac4399541551a018def7c137adbf Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 10 Feb 2023 17:49:07 +0530 Subject: [PATCH 098/334] Code comments resolved --- .../privacy/android/app/di/GetNodeModule.kt | 13 ++++++++- .../app/domain/usecase/OpenShareDialog.kt | 16 ++++++++++ .../android/app/main/ManagerActivity.java | 13 --------- .../NodeOptionsBottomSheetDialogFragment.java | 29 ++++--------------- .../recentactions/RecentActionsAdapter.kt | 15 +--------- .../recentactions/RecentActionsFragment.kt | 24 +-------------- .../recentactions/RecentActionsViewModel.kt | 10 ++++--- .../incoming/IncomingSharesViewModel.kt | 7 +---- .../outgoing/OutgoingSharesViewModel.kt | 15 ++++++++++ .../outgoing/model/OutgoingSharesState.kt | 3 -- .../assets/featuretoggle/feature_flags.json | 2 +- .../android/app/di/TestGetNodeModule.kt | 4 +++ .../RecentActionsViewModelTest.kt | 15 +++++++++- .../outgoing/OutgoingSharesViewModelTest.kt | 4 +++ .../data/repository/DefaultFilesRepository.kt | 0 15 files changed, 80 insertions(+), 90 deletions(-) create mode 100644 app/src/main/java/mega/privacy/android/app/domain/usecase/OpenShareDialog.kt delete mode 100644 data/src/main/java/mega/privacy/android/data/repository/DefaultFilesRepository.kt diff --git a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt index 0c723394994..a1d33f25bbc 100644 --- a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt @@ -8,9 +8,10 @@ import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.rx3.await import mega.privacy.android.app.domain.usecase.CheckNameCollision import mega.privacy.android.app.domain.usecase.CopyNode -import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle +import mega.privacy.android.app.domain.usecase.OpenShareDialog +import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.app.namecollision.usecase.CheckNameCollisionUseCase import mega.privacy.android.app.usecase.MoveNodeUseCase import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares @@ -139,5 +140,15 @@ abstract class GetNodeModule { @Provides fun provideSetSecureFlag(megaNodeRepository: MegaNodeRepository): SetSecureFlag = SetSecureFlag(megaNodeRepository::setSecureFlag) + + /** + * Provides [OpenShareDialog] implementation + * + * @param megaNodeRepository [MegaNodeRepository] + * @return [OpenShareDialog] + */ + @Provides + fun provideOpenShareDialog(megaNodeRepository: MegaNodeRepository): OpenShareDialog = + OpenShareDialog(megaNodeRepository::openShareDialog) } } diff --git a/app/src/main/java/mega/privacy/android/app/domain/usecase/OpenShareDialog.kt b/app/src/main/java/mega/privacy/android/app/domain/usecase/OpenShareDialog.kt new file mode 100644 index 00000000000..093b12f7c8d --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/domain/usecase/OpenShareDialog.kt @@ -0,0 +1,16 @@ +package mega.privacy.android.app.domain.usecase + +import nz.mega.sdk.MegaNode + +/** + * OpenShareDialog use case. This gets called when user shares a node using bottom sheet dialog + */ +fun interface OpenShareDialog { + + /** + * Invoke + * + * @param node : [MegaNode] + */ + suspend operator fun invoke(node: MegaNode) +} \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 00e0daf6bcc..9a2dfc46f3e 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -10309,19 +10309,6 @@ public void setChatBadge() { } } - public void setPendingActionsBadge(BottomNavigationMenuView menuView) { - ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { - if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { - BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); - View pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_pending_actions_badge, menuView, false); - sharedItemsView.addView(pendingActionsBadge); - TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.pending_actions_badge_text); - tvPendingActionsCount.setText(String.valueOf(managerState.getPendingActionsCount())); - } - return Unit.INSTANCE; - }); - } - private void setCallBadge() { if (!viewModel.isConnected() || megaChatApi.getNumCalls() <= 0 || (megaChatApi.getNumCalls() == 1 && participatingInACall())) { callBadge.setVisibility(View.GONE); diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 3273994fd1e..51d7548d5c9 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -76,9 +76,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; import kotlin.Unit; import mega.privacy.android.app.MegaOffline; @@ -88,14 +86,13 @@ import mega.privacy.android.app.arch.extensions.ViewExtensionsKt; import mega.privacy.android.app.imageviewer.ImageViewerActivity; import mega.privacy.android.app.interfaces.SnackbarShower; -import mega.privacy.android.app.listeners.OptionalMegaRequestListenerInterface; import mega.privacy.android.app.main.DrawerItem; import mega.privacy.android.app.main.FileContactListActivity; -import mega.privacy.android.app.presentation.fileinfo.FileInfoActivity; import mega.privacy.android.app.main.ManagerActivity; import mega.privacy.android.app.main.VersionsFileActivity; import mega.privacy.android.app.main.controllers.NodeController; import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity; +import mega.privacy.android.app.presentation.fileinfo.FileInfoActivity; import mega.privacy.android.app.presentation.manager.model.SharesTab; import mega.privacy.android.app.presentation.search.SearchViewModel; import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel; @@ -107,10 +104,7 @@ import mega.privacy.android.app.utils.ViewUtils; import mega.privacy.android.domain.entity.ShareData; import mega.privacy.android.domain.entity.SortOrder; -import nz.mega.sdk.MegaApiJava; -import nz.mega.sdk.MegaError; import nz.mega.sdk.MegaNode; -import nz.mega.sdk.MegaRequest; import nz.mega.sdk.MegaShare; import nz.mega.sdk.MegaUser; import timber.log.Timber; @@ -884,6 +878,7 @@ private void showOwnerSharedFolder() { } } } + private void setUnverifiedNodeUserName(List shareDataList) { for (int j = 0; j < shareDataList.size(); j++) { ShareData mS = shareDataList.get(j); @@ -894,6 +889,7 @@ private void setUnverifiedNodeUserName(List shareDataList) { } else { nodeInfo.setText(mS.getUser()); } + break; } } } @@ -1002,23 +998,8 @@ public void onClick(View v) { break; case R.id.share_folder_option: - if(incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() - && - (!node.isNodeKeyDecrypted() || - !megaApi.areCredentialsVerified( - megaApi.getContact(String.valueOf(node.getOwner()))) - ) - ) { - megaApi.openShareDialog(node, new OptionalMegaRequestListenerInterface() { - @Override - public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { - super.onRequestFinish(api, request, error); - showShareFolderOptions(); - } - }); - } else { - showShareFolderOptions(); - } + outgoingSharesViewModel.callOpenShareDialog(node.getHandle()); + showShareFolderOptions(); break; case R.id.clear_share_option: diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index 023dc0beeeb..b6f5288338c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -72,9 +72,6 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter - private lateinit var unverifiedOutgoingNodeHandles: HashSet - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecentActionViewHolder { val binding = ItemBucketBinding.inflate(LayoutInflater.from(parent.context), parent, false) @@ -194,7 +191,7 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter) { - unverifiedIncomingNodeHandles = HashSet() - unverifiedIncomingNodeHandles.addAll(handles) - } - - fun setUnverifiedOutgoingNodeHandles(handles: List) { - unverifiedOutgoingNodeHandles = HashSet() - unverifiedOutgoingNodeHandles.addAll(handles) - } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt index b971efcdc97..164f4422097 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt @@ -35,8 +35,6 @@ import mega.privacy.android.app.main.PdfViewerActivity import mega.privacy.android.app.modalbottomsheet.NodeOptionsBottomSheetDialogFragment import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity import mega.privacy.android.app.presentation.recentactions.model.RecentActionItemType -import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel -import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel import mega.privacy.android.app.utils.Constants import mega.privacy.android.app.utils.FileUtil import mega.privacy.android.app.utils.MegaApiUtils @@ -79,8 +77,6 @@ class RecentActionsFragment : Fragment() { private lateinit var fastScroller: FastScroller private val viewModel: RecentActionsViewModel by activityViewModels() - private val incomingSharesViewModel: IncomingSharesViewModel by activityViewModels() - private val outGoingSharesViewModel: OutgoingSharesViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, @@ -126,22 +122,6 @@ class RecentActionsFragment : Fragment() { fastScroller = binding.fastscroll initAdapter() - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - incomingSharesViewModel.state.collect { - adapter.setUnverifiedIncomingNodeHandles(it.unVerifiedIncomingNodeHandles) - } - } - } - - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - outGoingSharesViewModel.state.collect { - adapter.setUnverifiedOutgoingNodeHandles(it.unVerifiedOutgoingNodeHandles) - } - } - } - } /** @@ -150,9 +130,7 @@ class RecentActionsFragment : Fragment() { private fun initAdapter() { adapter.setOnItemClickListener { item, position -> - if (incomingSharesViewModel.state.value.unVerifiedIncomingNodeHandles.contains(item.bucket.nodes[0].id.longValue) - || outGoingSharesViewModel.state.value.unVerifiedOutgoingNodeHandles.contains(item.bucket.nodes[0].id.longValue) - ) { + if (!item.isKeyVerified) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, item.bucket.userEmail) requireActivity().startActivity(this) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index 65f371f02d3..8476c173583 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -18,6 +18,7 @@ import mega.privacy.android.app.presentation.recentactions.model.RecentActionsSh import mega.privacy.android.app.presentation.recentactions.model.RecentActionsState import mega.privacy.android.domain.entity.RecentActionBucket import mega.privacy.android.domain.entity.contacts.ContactItem +import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -35,6 +36,7 @@ import javax.inject.Inject * @param getVisibleContacts * @param setHideRecentActivity * @param monitorNodeUpdates + * @param areCredentialsVerified */ @HiltViewModel class RecentActionsViewModel @Inject constructor( @@ -47,6 +49,7 @@ class RecentActionsViewModel @Inject constructor( private val getParentMegaNode: GetParentMegaNode, monitorHideRecentActivity: MonitorHideRecentActivity, monitorNodeUpdates: MonitorNodeUpdates, + private val areCredentialsVerified: AreCredentialsVerified, ) : ViewModel() { private var _buckets = listOf() @@ -161,18 +164,17 @@ class RecentActionsViewModel @Inject constructor( visibleContacts.find { bucket.userEmail == it.email }?.contactData?.fullName.orEmpty() val currentUserIsOwner = getAccountDetails(false).email == bucket.userEmail - + val isNodeKeyVerified = + bucket.nodes[0].isNodeKeyDecrypted || areCredentialsVerified(bucket.userEmail) val parentNode = getNodeByHandle(bucket.parentHandle) val sharesType = getParentSharesType(parentNode) - val isNodeKeyDecrypted = - !bucket.nodes[0].isNodeKeyDecrypted && !areCredentialsVerified(bucket.userEmail) recentItemList.add(RecentActionItemType.Item( bucket, userName, parentNode?.name ?: "", sharesType, currentUserIsOwner, - isNodeKeyDecrypted, + isNodeKeyVerified, )) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 04c3aaafaf8..c1f8a0d2cf1 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -77,14 +77,10 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { val unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder) - val unverifiedIncomingNodes = unverifiedIncomingShares - .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .mapNotNull { shareData -> - getNodeByHandle(shareData.nodeHandle) - } val handles = unverifiedIncomingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .map { shareData -> shareData.nodeHandle } + val unverifiedIncomingNodes = handles.mapNotNull { getNodeByHandle(it) } _state.update { it.copy(nodes = unverifiedIncomingNodes, unverifiedIncomingShares = unverifiedIncomingShares, @@ -93,7 +89,6 @@ class IncomingSharesViewModel @Inject constructor( } } - /** * Refresh incoming shares node */ diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index d6703cd5faf..cf597d8c0bc 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates +import mega.privacy.android.app.domain.usecase.OpenShareDialog import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.model.OutgoingSharesState import mega.privacy.android.domain.usecase.GetCloudSortOrder @@ -36,6 +37,7 @@ class OutgoingSharesViewModel @Inject constructor( monitorNodeUpdates: MonitorNodeUpdates, private val getFeatureFlagValue: GetFeatureFlagValue, private val getUnverifiedOutgoingShares: GetUnverifiedOutgoingShares, + private val openShareDialog: OpenShareDialog, ) : ViewModel() { /** private UI state */ @@ -201,4 +203,17 @@ class OutgoingSharesViewModel @Inject constructor( it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) } } + + /** + * Calls OpenShareDialog use case to create crypto key for sharing + * + * @param nodeHandle: [MegaNode] handle + */ + fun callOpenShareDialog(nodeHandle: Long) { + viewModelScope.launch { + getNodeByHandle(nodeHandle)?.let { megaNode -> + openShareDialog(megaNode) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index fde4487f23b..b326a7e9987 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -2,7 +2,6 @@ package mega.privacy.android.app.presentation.shares.outgoing.model import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder -import mega.privacy.android.domain.usecase.AreCredentialsVerified import nz.mega.sdk.MegaNode /** @@ -18,7 +17,6 @@ import nz.mega.sdk.MegaNode * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles - * @param areUserCredentialsVerified Boolean value to read if user credentials are verified */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -31,7 +29,6 @@ data class OutgoingSharesState( val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodeHandles: List = emptyList(), - val areUserCredentialsVerified: Boolean = false, ) { /** diff --git a/app/src/qa/assets/featuretoggle/feature_flags.json b/app/src/qa/assets/featuretoggle/feature_flags.json index 903fef89531..57ca03d12da 100644 --- a/app/src/qa/assets/featuretoggle/feature_flags.json +++ b/app/src/qa/assets/featuretoggle/feature_flags.json @@ -9,6 +9,6 @@ }, { "name": "SetSecureFlag", - "value": false + "value": true } ] \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt index 29fc17817a7..f6af5e4c828 100644 --- a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt +++ b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt @@ -9,6 +9,7 @@ import mega.privacy.android.app.di.GetNodeModule import mega.privacy.android.app.domain.usecase.CopyNode import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle +import mega.privacy.android.app.domain.usecase.OpenShareDialog import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares @@ -47,4 +48,7 @@ object TestGetNodeModule { fun provideGetUnverifiedOutgoingShares() = mock() { onBlocking { invoke(any()) }.thenReturn(emptyList()) } + + @Provides + fun provideOpenShareDialog() = mock() } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt index e07fe51f2ff..17c68748bd1 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt @@ -24,6 +24,7 @@ import mega.privacy.android.domain.entity.contacts.ContactItem import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.entity.node.TypedFileNode +import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -101,6 +102,9 @@ class RecentActionsViewModelTest { on { this.isUpdate }.thenReturn(false) } + private val areCredentialsVerified = mock { + onBlocking { invoke(any()) }.thenReturn(true) + } @Before fun setUp() { @@ -115,6 +119,7 @@ class RecentActionsViewModelTest { getParentMegaNode, monitorHideRecentActivity, monitorNodeUpdates, + areCredentialsVerified, ) } @@ -439,5 +444,13 @@ class RecentActionsViewModelTest { assertThat(underTest.snapshotActionList).isEqualTo(expectedSnapshotActionList) } - + @Test + fun `test that isKeyVerified gets updated in recent action items`() = runTest { + whenever(getRecentActions()).thenReturn(listOf(megaRecentActionBucket)) + underTest.state.map { it.recentActionItems }.distinctUntilChanged().test { + awaitItem() + assertThat((awaitItem().filterIsInstance()[0]).isKeyVerified) + .isEqualTo(true) + } + } } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index a97d38b03e3..a23deb884d4 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode +import mega.privacy.android.app.domain.usecase.OpenShareDialog import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel import mega.privacy.android.domain.entity.ShareData @@ -65,6 +66,8 @@ class OutgoingSharesViewModelTest { onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } + private val openShareDialog = mock() + @Before fun setUp() { Dispatchers.setMain(UnconfinedTestDispatcher()) @@ -81,6 +84,7 @@ class OutgoingSharesViewModelTest { monitorNodeUpdates, getFeatureFlagValue, getUnverifiedOutgoingShares, + openShareDialog, ) } diff --git a/data/src/main/java/mega/privacy/android/data/repository/DefaultFilesRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/DefaultFilesRepository.kt deleted file mode 100644 index e69de29bb2d..00000000000 From 6524e913e0b3aeb2ed8a72b2e1f8de73ee4f152d Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 10 Feb 2023 17:56:06 +0530 Subject: [PATCH 099/334] Code comments resolved --- .../android/app/main/ManagerActivity.java | 20 +++++++++---------- .../app/main/adapters/MegaNodeAdapter.java | 11 +++++----- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 9a2dfc46f3e..a483ab5bb81 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -2509,6 +2509,15 @@ private void collectFlows() { } viewModel.nodeUpdateHandled(); } + + // Update pending actions badge on bottom navigation menu + if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { + BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); + View pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_pending_actions_badge, menuView, false); + sharedItemsView.addView(pendingActionsBadge); + TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.pending_actions_badge_text); + tvPendingActionsCount.setText(String.valueOf(managerState.getPendingActionsCount())); + } return Unit.INSTANCE; }); ViewExtensionsKt.collectFlow(this, viewModel.getOnViewTypeChanged(), Lifecycle.State.STARTED, viewType -> { @@ -2547,17 +2556,6 @@ private void collectFlows() { } return Unit.INSTANCE; }); - - ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { - if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { - BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); - View pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_pending_actions_badge, menuView, false); - sharedItemsView.addView(pendingActionsBadge); - TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.pending_actions_badge_text); - tvPendingActionsCount.setText(String.valueOf(managerState.getPendingActionsCount())); - } - return Unit.INSTANCE; - }); } /** diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index c8626a330e0..1101a22b026 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1092,10 +1092,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - - if (isMandatoryFingerprintVerificationNeeded + boolean hasUnverifiedNodes = isMandatoryFingerprintVerificationNeeded && !unverifiedIncomingNodeHandles.isEmpty() - && unverifiedIncomingNodeHandles.contains(node.getHandle())) { + && unverifiedIncomingNodeHandles.contains(node.getHandle()); + if (hasUnverifiedNodes) { showUnverifiedNodeUi(holder, true); } holder.permissionsIcon.setVisibility(View.VISIBLE); @@ -1106,9 +1106,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else if (type == OUTGOING_SHARES_ADAPTER) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - if (isMandatoryFingerprintVerificationNeeded + boolean hasUnverifiedNodes = isMandatoryFingerprintVerificationNeeded && !unverifiedOutgoingNodeHandles.isEmpty() - && unverifiedOutgoingNodeHandles.contains(node.getHandle())) { + && unverifiedOutgoingNodeHandles.contains(node.getHandle()); + if (hasUnverifiedNodes) { showUnverifiedNodeUi(holder, false); } } From e121b9ba2c9d1b46891e2ce746352b45a8eb4697 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 10 Feb 2023 18:10:47 +0530 Subject: [PATCH 100/334] code comments resolved --- .../presentation/shares/incoming/IncomingSharesViewModel.kt | 5 ++--- .../presentation/shares/outgoing/OutgoingSharesViewModel.kt | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index c1f8a0d2cf1..2d4a7343adb 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -77,10 +77,9 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { val unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder) - val handles = unverifiedIncomingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .map { shareData -> shareData.nodeHandle } - val unverifiedIncomingNodes = handles.mapNotNull { getNodeByHandle(it) } + val handles = unverifiedIncomingShares.map { shareData -> shareData.nodeHandle } + val unverifiedIncomingNodes = handles.mapNotNull { handle -> getNodeByHandle(handle) } _state.update { it.copy(nodes = unverifiedIncomingNodes, unverifiedIncomingShares = unverifiedIncomingShares, diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index cf597d8c0bc..8a20536d57d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -64,8 +64,8 @@ class OutgoingSharesViewModel @Inject constructor( viewModelScope.launch { val unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder) - val handles = unverifiedOutgoingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + val handles = unverifiedOutgoingShares .map { shareData -> shareData.nodeHandle } _state.update { it.copy(unverifiedOutgoingShares = unverifiedOutgoingShares, From c837d0583705d36adfd36325ecec3ff054d0fcca Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 10 Feb 2023 18:46:22 +0530 Subject: [PATCH 101/334] TabItem added to tab layout file because tabLayoutShares.getTabAt returned null --- app/src/main/res/layout/activity_manager.xml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/activity_manager.xml b/app/src/main/res/layout/activity_manager.xml index 744e1843f11..0cc82de0875 100644 --- a/app/src/main/res/layout/activity_manager.xml +++ b/app/src/main/res/layout/activity_manager.xml @@ -36,7 +36,20 @@ android:id="@+id/sliding_tabs_shares" style="@style/Widget.Mega.TabLayout" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content"> + + + + + android:layout_weight="1" > + Date: Mon, 13 Feb 2023 16:18:58 +1300 Subject: [PATCH 102/334] Close app on click of cancel of Security upgrade dialog --- .../privacy/android/app/di/GetNodeModule.kt | 10 +++ .../SecurityUpgradeDialogFragment.kt | 64 +++++++++++++++++++ .../SecurityUpgradeViewModel.kt | 26 ++++++++ .../android/app/di/TestGetNodeModule.kt | 4 ++ .../SecurityUpgradeViewModelTest.kt | 39 +++++++++++ .../android/domain/usecase/UpgradeSecurity.kt | 12 ++++ 6 files changed, 155 insertions(+) create mode 100644 app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt create mode 100644 app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModel.kt create mode 100644 app/src/test/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModelTest.kt create mode 100644 domain/src/main/kotlin/mega/privacy/android/domain/usecase/UpgradeSecurity.kt diff --git a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt index a1d33f25bbc..5eef302b045 100644 --- a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt @@ -20,6 +20,7 @@ import mega.privacy.android.domain.usecase.SetSecureFlag import mega.privacy.android.domain.usecase.filenode.CopyNodeByHandle import mega.privacy.android.domain.usecase.filenode.CopyNodeByHandleChangingName import mega.privacy.android.domain.usecase.filenode.MoveNodeByHandle +import mega.privacy.android.domain.usecase.UpgradeSecurity /** * Get node module @@ -150,5 +151,14 @@ abstract class GetNodeModule { @Provides fun provideOpenShareDialog(megaNodeRepository: MegaNodeRepository): OpenShareDialog = OpenShareDialog(megaNodeRepository::openShareDialog) + + /** + * Provides [UpgradeSecurity] implementation + * @param megaNodeRepository [MegaNodeRepository] + * @return [UpgradeSecurity] + */ + @Provides + fun provideUpgradeSecurity(megaNodeRepository: MegaNodeRepository): UpgradeSecurity = + UpgradeSecurity(megaNodeRepository::upgradeSecurity) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt new file mode 100644 index 00000000000..b2edf33400a --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt @@ -0,0 +1,64 @@ +package mega.privacy.android.app.presentation.fingerprintauth + +import android.app.Dialog +import android.os.Bundle +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.viewModels +import collectAsStateWithLifecycle +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import mega.privacy.android.app.presentation.extensions.isDarkMode +import mega.privacy.android.core.ui.theme.AndroidTheme +import mega.privacy.android.domain.entity.ThemeMode +import mega.privacy.android.domain.usecase.GetThemeMode +import javax.inject.Inject + +/** + * SecurityUpgradeDialogFragment + */ +@AndroidEntryPoint +class SecurityUpgradeDialogFragment : DialogFragment() { + + private val securityUpgradeViewModel by viewModels() + + /** + * Current theme + */ + @Inject + lateinit var getThemeMode: GetThemeMode + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = + MaterialAlertDialogBuilder(requireContext()).setView( + ComposeView(requireContext()).apply { + setContent { + val nodeName = arguments?.getString("nodeName", "Default") as String + val mode by getThemeMode() + .collectAsStateWithLifecycle(initialValue = ThemeMode.System) + AndroidTheme(isDark = mode.isDarkMode()) { + SecurityUpgradeDialogView(folderName = nodeName, onCancelClick = { + requireActivity().finishAffinity() + }, onOkClick = { + securityUpgradeViewModel.upgradeAccountSecurity() + dismiss() + }) + } + } + } + ).create() + + companion object { + /** + * Tag for logging + */ + const val TAG = "SecurityUpgradeDialogFragment" + + /** + * Creates instance of this class + * + * @return SecurityUpgradeDialogFragment new instance + */ + fun newInstance() = SecurityUpgradeDialogFragment() + } +} \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModel.kt new file mode 100644 index 00000000000..37511a045c0 --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModel.kt @@ -0,0 +1,26 @@ +package mega.privacy.android.app.presentation.fingerprintauth + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import mega.privacy.android.domain.usecase.UpgradeSecurity +import javax.inject.Inject + +/** + * ViewModel associated with [SecurityUpgradeDialogFragment] responsible to call account security related functions + */ +@HiltViewModel +class SecurityUpgradeViewModel @Inject constructor( + private val upgradeSecurity: UpgradeSecurity, +) : ViewModel() { + + /** + * Function to upgrade account security + */ + fun upgradeAccountSecurity() { + viewModelScope.launch { + upgradeSecurity() + } + } +} \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt index f6af5e4c828..0aeed73af39 100644 --- a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt +++ b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt @@ -13,6 +13,7 @@ import mega.privacy.android.app.domain.usecase.OpenShareDialog import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares +import mega.privacy.android.domain.usecase.UpgradeSecurity import nz.mega.sdk.MegaNode import org.mockito.kotlin.any import org.mockito.kotlin.mock @@ -51,4 +52,7 @@ object TestGetNodeModule { @Provides fun provideOpenShareDialog() = mock() + + @Provides + fun provideUpgradeSecurity() = mock() } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModelTest.kt new file mode 100644 index 00000000000..d1042310d6f --- /dev/null +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModelTest.kt @@ -0,0 +1,39 @@ +package test.mega.privacy.android.app.presentation.fingerprintauth + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import mega.privacy.android.app.presentation.fingerprintauth.SecurityUpgradeViewModel +import mega.privacy.android.domain.usecase.UpgradeSecurity +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@ExperimentalCoroutinesApi +class SecurityUpgradeViewModelTest { + + private lateinit var underTest: SecurityUpgradeViewModel + private val upgradeSecurity = mock() + + @Before + fun setUp() { + Dispatchers.setMain(UnconfinedTestDispatcher()) + underTest = SecurityUpgradeViewModel(upgradeSecurity) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + } + + @Test + fun `test that upgrade security is invoked`() = runTest { + underTest.upgradeAccountSecurity() + verify(upgradeSecurity).invoke() + } +} \ No newline at end of file diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/UpgradeSecurity.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/UpgradeSecurity.kt new file mode 100644 index 00000000000..f91caa630a6 --- /dev/null +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/UpgradeSecurity.kt @@ -0,0 +1,12 @@ +package mega.privacy.android.domain.usecase + +/** + * Upgrade security use case + */ +fun interface UpgradeSecurity { + + /** + * Invoke + */ + suspend operator fun invoke() +} \ No newline at end of file From a6afaddf7a3e0ca3d064a5fe8ae3340633b754a8 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 13 Feb 2023 18:30:07 +0530 Subject: [PATCH 103/334] setSecureFlag changed to false --- app/src/qa/assets/featuretoggle/feature_flags.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/qa/assets/featuretoggle/feature_flags.json b/app/src/qa/assets/featuretoggle/feature_flags.json index 57ca03d12da..903fef89531 100644 --- a/app/src/qa/assets/featuretoggle/feature_flags.json +++ b/app/src/qa/assets/featuretoggle/feature_flags.json @@ -9,6 +9,6 @@ }, { "name": "SetSecureFlag", - "value": true + "value": false } ] \ No newline at end of file From c0edb2aeac34846decee83093a05a6270dd7ce0f Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 14 Feb 2023 11:17:26 +0530 Subject: [PATCH 104/334] Latest transifex strings downloaded --- .../app/di/featuretoggle/FeatureFlagModule.kt | 2 +- .../android/app/main/ManagerActivity.java | 14 +- .../NodeOptionsBottomSheetDialogFragment.java | 19 +- .../outgoing/OutgoingSharesViewModel.kt | 18 +- .../outgoing/model/OutgoingSharesState.kt | 4 + app/src/main/res/values/strings.xml | 485 ++++++++++++++++-- 6 files changed, 482 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt b/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt index 2e71d60bf55..38046e9b04f 100644 --- a/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt @@ -73,7 +73,7 @@ abstract class FeatureFlagModule { /** * Provides [SetSecureFlag] implementation * - * @param filesRepository [FilesRepository] + * @param megaNodeRepository [MegaNodeRepository] * @return [SetSecureFlag] */ @Provides diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index a483ab5bb81..30f16276b51 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -1322,13 +1322,6 @@ protected void onCreate(Bundle savedInstanceState) { return null; })); - ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, state -> { - if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { - replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); - } - return Unit.INSTANCE; - }); - collectFlows(); viewModel.onGetNumUnreadUserAlerts().observe(this, this::updateNumUnreadUserAlerts); @@ -2556,6 +2549,13 @@ private void collectFlows() { } return Unit.INSTANCE; }); + + ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, state -> { + if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { + replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); + } + return Unit.INSTANCE; + }); } /** diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 51d7548d5c9..b533c985362 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -898,7 +898,11 @@ private void hideNodeActions() { TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); optionVerifyUser.setText(StringResourcesUtils.getString(R.string.shared_items_bottom_sheet_menu_verify_user, getMegaUserNameDB(user))); TextView nodeName = contentView.findViewById(R.id.node_name_text); - nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + if(nC.nodeComesFromIncoming(node)) { + nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + } else { + nodeName.setText(node.getName()); + } optionVerifyUser.setVisibility(View.VISIBLE); optionVerifyUser.setOnClickListener(this); @@ -999,7 +1003,18 @@ public void onClick(View v) { case R.id.share_folder_option: outgoingSharesViewModel.callOpenShareDialog(node.getHandle()); - showShareFolderOptions(); + ViewExtensionsKt.collectFlow(requireActivity(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { + if (state.isOpenShareDialogSuccess()) { + showShareFolderOptions(); + } else { + if(state.getErrorMessage() != null) { + showSnackbar(requireActivity(), state.getErrorMessage()); + } else { + showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); + } + } + return Unit.INSTANCE; + }); break; case R.id.clear_share_option: diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 8a20536d57d..746114e3581 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -210,9 +210,21 @@ class OutgoingSharesViewModel @Inject constructor( * @param nodeHandle: [MegaNode] handle */ fun callOpenShareDialog(nodeHandle: Long) { - viewModelScope.launch { - getNodeByHandle(nodeHandle)?.let { megaNode -> - openShareDialog(megaNode) + kotlin.runCatching { + viewModelScope.launch { + if (!isInvalidHandle(nodeHandle)) { + getNodeByHandle(nodeHandle)?.let { megaNode -> + openShareDialog(megaNode) + } + } + } + }.onSuccess { + _state.update { + it.copy(isOpenShareDialogSuccess = true) + } + }.onFailure { error -> + _state.update { + it.copy(errorMessage = error.message) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index b326a7e9987..0ef0cc11f19 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -17,6 +17,8 @@ import nz.mega.sdk.MegaNode * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles + * @param isOpenShareDialogSuccess if openShareDialog API call is a success or failure + * @param errorMessage Error message to show on UI */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -29,6 +31,8 @@ data class OutgoingSharesState( val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodeHandles: List = emptyList(), + val isOpenShareDialogSuccess: Boolean = false, + val errorMessage: String? = null ) { /** diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index db8b404b1b0..4884853604e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -158,8 +158,6 @@ Connecting to the server Updating file list - - Confirm account Checking validation link @@ -170,8 +168,6 @@ Please log in to share with MEGA Your confirmation link is no longer valid. Your account may already be activated or you may have cancelled your registration. - - Name First name @@ -311,8 +307,6 @@ Folders are empty. - Touch to cancel - View transfers Most devices can’t download files greater than 4 GB. Your download will probably fail. @@ -1506,8 +1500,6 @@ You have received %1$s storage space for verifying your phone number. You have received %1$s storage space as your free registration bonus. - - Bonus expires in %1$d days Share folder @@ -2114,8 +2106,6 @@ Logged out Your account has been activated. Please log in. - - Please enter your password to confirm your account There’s no need to add your own email address @@ -2350,8 +2340,6 @@ Continue [A]Google Pay[/A] (subscription) - - [A]HUAWEI Pay[/A] (subscription) NEW @@ -2863,8 +2851,6 @@ Select messages Failed - - Your transfers have been interrupted. Upgrade your account or wait %s to continue. Transfer quota exceeded @@ -3214,10 +3200,6 @@ Months Years - - [B]%1$s[/B] %2$s - - [B]%1$s %2$s[/B], %3$s Upload in progress, 1 file pending @@ -3583,22 +3565,6 @@ Payment methods Proceed - - Pro Lite monthly - - Pro Lite yearly - - Pro I monthly - - Pro I yearly - - Pro II monthly - - Pro II yearly - - Pro III monthly - - Pro III yearly Remove as host @@ -3933,8 +3899,6 @@ Start chatting now Chat securely and privately, with anyone and on any device, knowing that no one can read your chats, not even MEGA. - - Archive meeting Start meeting now @@ -4027,8 +3991,6 @@ You’ve already added all your contacts to this chat. If you want to add more participants, first invite them to your contact list. Saved image to your device gallery - - [A]Renewal date:[/A] [B]%s[/B] Enter album name @@ -4166,29 +4128,458 @@ [A]%s [/A][B]invited you to a meeting scheduled for:[/B] - [A]%s [/A][B]cancelled the meeting scheduled for:[/B] + [A]%s cancelled[/A][B] the meeting scheduled for:[/B] - [A]%s [/A][B]updated the meeting name from “%s” to [/B]“[A]%s[/A]“ + [A]%s updated the meeting name[/A][B] from “%s” to [/B]“[A]%s[/A]” - [A]%s [/A][B]updated the meeting date[/B] + [A]%s updated[/A][B] the meeting date[/B] - [A]%s [/A][B]updated the meeting time[/B] + [A]%s updated[/A][B] the meeting time[/B] - [A]%s [/A][B]updated the meeting description[/B] + [A]%s updated[/A][B] the meeting description[/B] - [A]%s [/A][B]updated the meeting details scheduled for:[/B] + [A]%s updated[/A][B] the meeting details scheduled for:[/B] - Access Denied + Access denied You denied MEGA access to your device’s storage and media files. If you’d like to continue sharing, allow MEGA permission. Allow permission Don’t allow + + Occurrences + + Occurs daily + + Occurs weekly + + Occurs monthly + + See more occurrences + + %s monthly + + %s daily + + [A]%s [/A][B]invited you to a recurring meeting scheduled for:[/B] + + [A]%s updated[/A][B] the recurring meeting description[/B] + + [A]%s updated[/A][B] the recurring meeting details scheduled for:[/B] + + [A]%s updated[/A][B] an occurrence to:[/B] + + [A]%s updated[/A][B] the recurring meeting time[/B] + + [A]%s updated[/A][B] the recurring meeting frequency[/B] + + [A]%s cancelled[/A][B] the meeting and all its occurrences[/B] + + [A]%s cancelled[/A][B] the occurrence scheduled for:[/B] + + This link hasn’t been generated with the account you’re currently logged into. Log in to the account related to this link to verify your email address. + + Unable to update email address + + Select album cover + + Album cover updated + + %1$s from %2$s to %3$s + + Everyday effective %1$s from %2$s to %3$s + + Everyday effective %1$s until %2$s from %3$s to %4$s + + + %1$s every week effective %3$s until %4$s from %5$s to %6$s + %1$s every %2$d weeks effective %3$s until %4$s from %5$s to %6$s + + + + %1$s every week effective %3$s from %4$s to %5$s + %1$s every %2$d weeks effective %3$s from %4$s to %5$s + + + + %1$s and %2$s every week effective %4$s until %5$s from %6$s to %7$s + %1$s and %2$s every %3$d weeks effective %4$s until %5$s from %6$s to %7$s + + + + %1$s and %2$s every week effective %4$s from %5$s to %6$s + %1$s and %2$s every %3$d weeks effective %4$s from %5$s to %6$s + + + + Day %1$d of every month effective %3$s until %4$s from %5$s to %6$s + Day %1$d of every %2$d months effective %3$s until %4$s from %5$s to %6$s + + + + Day %1$d of every month effective %3$s from %4$s to %5$s + Day %1$d of every %2$d months effective %3$s from %4$s to %5$s + + + + First Monday of every month effective %2$s until %3$s from %4$s to %5$s + First Monday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Monday of every month effective %2$s from %3$s to %4$s + First Monday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Monday of every month effective %2$s until %3$s from %4$s to %5$s + Second Monday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Monday of every month effective %2$s from %3$s to %4$s + Second Monday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Monday of every month effective %2$s until %3$s from %4$s to %5$s + Third Monday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Monday of every month effective %2$s from %3$s to %4$s + Third Monday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Monday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Monday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Monday of every month effective %2$s from %3$s to %4$s + Fourth Monday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Monday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Monday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Monday of every month effective %2$s from %3$s to %4$s + Fifth Monday of every %1$d months effective %2$s from %3$s to %4$s + + + + First Tuesday of every month effective %2$s until %3$s from %4$s to %5$s + First Tuesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Tuesday of every month effective %2$s from %3$s to %4$s + First Tuesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Tuesday of every month effective %2$s until %3$s from %4$s to %5$s + Second Tuesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Tuesday of every month effective %2$s from %3$s to %4$s + Second Tuesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Tuesday of every month effective %2$s until %3$s from %4$s to %5$s + Third Tuesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Tuesday of every month effective %2$s from %3$s to %4$s + Third Tuesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Tuesday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Tuesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Tuesday of every month effective %2$s from %3$s to %4$s + Fourth Tuesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Tuesday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Tuesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Tuesday of every month effective %2$s from %3$s to %4$s + Fifth Tuesday of every %1$d months effective %2$s from %3$s to %4$s + + + + First Wednesday of every month effective %2$s until %3$s from %4$s to %5$s + First Wednesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Wednesday of every month effective %2$s from %3$s to %4$s + First Wednesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Wednesday of every month effective %2$s until %3$s from %4$s to %5$s + Second Wednesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Wednesday of every month effective %2$s from %3$s to %4$s + Second Wednesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Wednesday of every month effective %2$s until %3$s from %4$s to %5$s + Third Wednesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Wednesday of every month effective %2$s from %3$s to %4$s + Third Wednesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Wednesday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Wednesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Wednesday of every month effective %2$s from %3$s to %4$s + Fourth Wednesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Wednesday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Wednesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Wednesday of every month effective %2$s from %3$s to %4$s + Fifth Wednesday of every %1$d months effective %2$s from %3$s to %4$s + + + + First Thursday of every month effective %2$s until %3$s from %4$s to %5$s + First Thursday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Thursday of every month effective %2$s from %3$s to %4$s + First Thursday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Thursday of every month effective %2$s until %3$s from %4$s to %5$s + Second Thursday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Thursday of every month effective %2$s from %3$s to %4$s + Second Thursday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Thursday of every month effective %2$s until %3$s from %4$s to %5$s + Third Thursday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Thursday of every month effective %2$s from %3$s to %4$s + Third Thursday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Thursday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Thursday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Thursday of every month effective %2$s from %3$s to %4$s + Fourth Thursday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Thursday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Thursday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Thursday of every month effective %2$s from %3$s to %4$s + Fifth Thursday of every %1$d months effective %2$s from %3$s to %4$s + + + + First Friday of every month effective %2$s until %3$s from %4$s to %5$s + First Friday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Friday of every month effective %2$s from %3$s to %4$s + First Friday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Friday of every month effective %2$s until %3$s from %4$s to %5$s + Second Friday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Friday of every month effective %2$s from %3$s to %4$s + Second Friday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Friday of every month effective %2$s until %3$s from %4$s to %5$s + Third Friday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Friday of every month effective %2$s from %3$s to %4$s + Third Friday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Friday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Friday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Friday of every month effective %2$s from %3$s to %4$s + Fourth Friday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Friday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Friday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Friday of every month effective %2$s from %3$s to %4$s + Fifth Friday of every %1$d months effective %2$s from %3$s to %4$s + + + + First Saturday of every month effective %2$s until %3$s from %4$s to %5$s + First Saturday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Saturday of every month effective %2$s from %3$s to %4$s + First Saturday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Saturday of every month effective %2$s until %3$s from %4$s to %5$s + Second Saturday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Saturday of every month effective %2$s from %3$s to %4$s + Second Saturday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Saturday of every month effective %2$s until %3$s from %4$s to %5$s + Third Saturday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Saturday of every month effective %2$s from %3$s to %4$s + Third Saturday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Saturday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Saturday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Saturday of every month effective %2$s from %3$s to %4$s + Fourth Saturday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Saturday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Saturday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Saturday of every month effective %2$s from %3$s to %4$s + Fifth Saturday of every %1$d months effective %2$s from %3$s to %4$s + + + + First Sunday of every month effective %2$s until %3$s from %4$s to %5$s + First Sunday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Sunday of every month effective %2$s from %3$s to %4$s + First Sunday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Sunday of every month effective %2$s until %3$s from %4$s to %5$s + Second Sunday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Sunday of every month effective %2$s from %3$s to %4$s + Second Sunday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Sunday of every month effective %2$s until %3$s from %4$s to %5$s + Third Sunday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Sunday of every month effective %2$s from %3$s to %4$s + Third Sunday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Sunday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Sunday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Sunday of every month effective %2$s from %3$s to %4$s + Fourth Sunday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Sunday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Sunday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Sunday of every month effective %2$s from %3$s to %4$s + Fifth Sunday of every %1$d months effective %2$s from %3$s to %4$s + + Security upgrade + Your account’s security is now being upgraded. This will happen only once. If you’ve seen this message for this account before, tap Cancel. - You’re currently sharing the following folders: %s - + + + You’re currently sharing the following folder: %s + You’re currently sharing the following folders: %s + [Undecrypted file] From 9b6e00ddc96b654ce33f6f802d9c0ebbdf54270e Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 14 Feb 2023 12:28:38 +0530 Subject: [PATCH 105/334] Multiple folder names displayed on security upgrade dialog --- .../SecurityUpgradeDialogFragment.kt | 4 ++-- .../fingerprintauth/SecurityUpgradeDialogView.kt | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt index b2edf33400a..3591e9ff805 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt @@ -33,11 +33,11 @@ class SecurityUpgradeDialogFragment : DialogFragment() { MaterialAlertDialogBuilder(requireContext()).setView( ComposeView(requireContext()).apply { setContent { - val nodeName = arguments?.getString("nodeName", "Default") as String + val nodeName = arguments?.getStringArrayList("nodeName") as List val mode by getThemeMode() .collectAsStateWithLifecycle(initialValue = ThemeMode.System) AndroidTheme(isDark = mode.isDarkMode()) { - SecurityUpgradeDialogView(folderName = nodeName, onCancelClick = { + SecurityUpgradeDialogView(folderNames = nodeName, onCancelClick = { requireActivity().finishAffinity() }, onOkClick = { securityUpgradeViewModel.upgradeAccountSecurity() diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt index 96e9afe03cb..2ba5c4a08dd 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt @@ -1,5 +1,6 @@ package mega.privacy.android.app.presentation.fingerprintauth +import android.text.TextUtils import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -16,18 +17,20 @@ import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import mega.privacy.android.app.R -import mega.privacy.android.presentation.theme.body2 -import mega.privacy.android.presentation.theme.jade_300 -import mega.privacy.android.presentation.theme.subtitle1 +import mega.privacy.android.core.ui.theme.body2 +import mega.privacy.android.core.ui.theme.jade_300 +import mega.privacy.android.core.ui.theme.subtitle1 /** * Security upgrade dialog body @@ -36,9 +39,10 @@ import mega.privacy.android.presentation.theme.subtitle1 * @param onOkClick : Ok button click listener * @param onCancelClick : Cancel button click listener */ +@OptIn(ExperimentalComposeUiApi::class) @Composable fun SecurityUpgradeDialogView( - folderName: String, + folderNames: List, onOkClick: () -> Unit, onCancelClick: () -> Unit, ) { @@ -83,8 +87,8 @@ fun SecurityUpgradeDialogView( Spacer(Modifier.height(20.dp)) Text(modifier = Modifier.testTag("SharedNodeInfo"), - text = stringResource(id = R.string.shared_items_security_upgrade_dialog_node_sharing_info, - folderName), + text = pluralStringResource(id = R.plurals.shared_items_security_upgrade_dialog_node_sharing_info, + folderNames.size, TextUtils.join(", ", folderNames)), style = body2.copy(textAlign = TextAlign.Center), color = if (MaterialTheme.colors.isLight) { Color.Black From 47f6ef4da13de1ee33b15080bd7de3e8c44a77cb Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 14 Feb 2023 13:36:44 +0530 Subject: [PATCH 106/334] Removed repetitive collection of flow --- .../privacy/android/app/main/ManagerActivity.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 30f16276b51..d48d8f41b58 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -2490,6 +2490,11 @@ private void collectFlows() { }); ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { + + if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { + replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); + } + updateInboxSectionVisibility(managerState.getHasInboxChildren()); stopUploadProcessAndSendBroadcast(managerState.getShouldStopCameraUpload(), managerState.getShouldSendCameraBroadcastEvent()); if (managerState.getNodeUpdateReceived()) { @@ -2549,13 +2554,6 @@ private void collectFlows() { } return Unit.INSTANCE; }); - - ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, state -> { - if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { - replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); - } - return Unit.INSTANCE; - }); } /** From 5c156564bc15afac20d3fadbbc15037e9c61669d Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 14 Feb 2023 14:20:25 +0530 Subject: [PATCH 107/334] API error removed & generic message is displayed --- .../NodeOptionsBottomSheetDialogFragment.java | 6 +----- .../presentation/shares/outgoing/OutgoingSharesViewModel.kt | 4 ++-- .../shares/outgoing/model/OutgoingSharesState.kt | 2 -- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index b533c985362..8b0ed139dd2 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -1007,11 +1007,7 @@ public void onClick(View v) { if (state.isOpenShareDialogSuccess()) { showShareFolderOptions(); } else { - if(state.getErrorMessage() != null) { - showSnackbar(requireActivity(), state.getErrorMessage()); - } else { - showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); - } + showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); } return Unit.INSTANCE; }); diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 746114e3581..40d63801c62 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -222,9 +222,9 @@ class OutgoingSharesViewModel @Inject constructor( _state.update { it.copy(isOpenShareDialogSuccess = true) } - }.onFailure { error -> + }.onFailure { _state.update { - it.copy(errorMessage = error.message) + it.copy(isOpenShareDialogSuccess = false) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index 0ef0cc11f19..cccd55fa233 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -18,7 +18,6 @@ import nz.mega.sdk.MegaNode * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles * @param isOpenShareDialogSuccess if openShareDialog API call is a success or failure - * @param errorMessage Error message to show on UI */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -32,7 +31,6 @@ data class OutgoingSharesState( val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodeHandles: List = emptyList(), val isOpenShareDialogSuccess: Boolean = false, - val errorMessage: String? = null ) { /** From b415b83d39e980c48441c71d1ea6c664e2e90323 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 14 Feb 2023 14:57:14 +0530 Subject: [PATCH 108/334] Moved flow collection into onViewCreated. --- .../NodeOptionsBottomSheetDialogFragment.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 8b0ed139dd2..22ef0afd792 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -718,7 +718,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); if(nC.nodeComesFromIncoming(node)) { - ViewExtensionsKt.collectFlow(requireActivity(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { + ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { @@ -728,13 +728,20 @@ && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { return Unit.INSTANCE; }); } else { - ViewExtensionsKt.collectFlow(requireActivity(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { + ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE && isNodeUnverified(state.getUnVerifiedOutgoingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedOutgoingShares()); hideNodeActions(); } + + if (state.isOpenShareDialogSuccess()) { + showShareFolderOptions(); + } else { + showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); + } + return Unit.INSTANCE; }); } @@ -1003,14 +1010,6 @@ public void onClick(View v) { case R.id.share_folder_option: outgoingSharesViewModel.callOpenShareDialog(node.getHandle()); - ViewExtensionsKt.collectFlow(requireActivity(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (state.isOpenShareDialogSuccess()) { - showShareFolderOptions(); - } else { - showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); - } - return Unit.INSTANCE; - }); break; case R.id.clear_share_option: From 05ac93d0f957bd309ff567bade500c879df97542 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 15 Feb 2023 02:14:57 +1300 Subject: [PATCH 109/334] AND-15672 NodeOptionsBottomSheetViewModel created specifically for NodeOptionsBottomSheetDialogFragment --- .../NodeOptionsBottomSheetDialogFragment.java | 20 +++--- .../NodeOptionsBottomSheetState.kt | 12 ++++ .../NodeOptionsBottomSheetViewModel.kt | 72 +++++++++++++++++++ .../outgoing/OutgoingSharesViewModel.kt | 27 ------- .../outgoing/model/OutgoingSharesState.kt | 2 - .../NodeOptionsBottomSheetViewModelTest.kt | 61 ++++++++++++++++ .../outgoing/OutgoingSharesViewModelTest.kt | 4 -- 7 files changed, 157 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt create mode 100644 app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt create mode 100644 app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 22ef0afd792..23fcb7d7e88 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -175,6 +175,7 @@ public class NodeOptionsBottomSheetDialogFragment extends BaseBottomSheetDialogF private IncomingSharesViewModel incomingSharesViewModel; private OutgoingSharesViewModel outgoingSharesViewModel; + private NodeOptionsBottomSheetViewModel nodeOptionsBottomSheetViewModel; public NodeOptionsBottomSheetDialogFragment(int mode) { if (mode >= DEFAULT_MODE && mode <= FAVOURITES_MODE) { @@ -192,6 +193,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class); incomingSharesViewModel = new ViewModelProvider(requireActivity()).get(IncomingSharesViewModel.class); outgoingSharesViewModel = new ViewModelProvider(requireActivity()).get(OutgoingSharesViewModel.class); + nodeOptionsBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(NodeOptionsBottomSheetViewModel.class); } @Nullable @@ -735,16 +737,18 @@ && isNodeUnverified(state.getUnVerifiedOutgoingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedOutgoingShares()); hideNodeActions(); } - - if (state.isOpenShareDialogSuccess()) { - showShareFolderOptions(); - } else { - showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); - } - return Unit.INSTANCE; }); } + + ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), nodeOptionsBottomSheetViewModel.getState(), Lifecycle.State.STARTED, state -> { + if (state.isOpenShareDialogSuccess()) { + showShareFolderOptions(); + } else { + showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); + } + return Unit.INSTANCE; + }); } @Override @@ -1009,7 +1013,7 @@ public void onClick(View v) { break; case R.id.share_folder_option: - outgoingSharesViewModel.callOpenShareDialog(node.getHandle()); + nodeOptionsBottomSheetViewModel.callOpenShareDialog(node.getHandle()); break; case R.id.clear_share_option: diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt new file mode 100644 index 00000000000..b9873472936 --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt @@ -0,0 +1,12 @@ +package mega.privacy.android.app.modalbottomsheet + +/** + * Node options UI state + * + * @param currentNodeHandle Node handle of the current node for which bottom sheet dialog is opened + * @param isOpenShareDialogSuccess if openShareDialog API call is a success or failure + */ +data class NodeOptionsBottomSheetState( + val currentNodeHandle: Long = -1L, + val isOpenShareDialogSuccess: Boolean = false, +) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt new file mode 100644 index 00000000000..cf70e2ad2ac --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt @@ -0,0 +1,72 @@ +package mega.privacy.android.app.modalbottomsheet + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import mega.privacy.android.app.domain.usecase.GetNodeByHandle +import mega.privacy.android.app.domain.usecase.OpenShareDialog +import nz.mega.sdk.MegaApiJava +import nz.mega.sdk.MegaNode +import javax.inject.Inject + +/** + * View model associated with [NodeOptionsBottomSheetDialogFragment] + */ +@HiltViewModel +class NodeOptionsBottomSheetViewModel @Inject constructor( + private val openShareDialog: OpenShareDialog, + private val getNodeByHandle: GetNodeByHandle, +) : ViewModel() { + + /** + * Private UI state + */ + private val _state = MutableStateFlow(NodeOptionsBottomSheetState()) + + /** + * Public Ui state + */ + val state: StateFlow = _state + + /** + * Check if the handle is valid or not + * + * @param handle + * @return true if the handle is invalid + */ + private suspend fun isInvalidHandle(handle: Long = _state.value.currentNodeHandle): Boolean { + return handle + .takeUnless { it == -1L || it == MegaApiJava.INVALID_HANDLE } + ?.let { getNodeByHandle(it) == null } + ?: true + } + + /** + * Calls OpenShareDialog use case to create crypto key for sharing + * + * @param nodeHandle: [MegaNode] handle + */ + fun callOpenShareDialog(nodeHandle: Long) { + kotlin.runCatching { + viewModelScope.launch { + if (!isInvalidHandle(nodeHandle)) { + getNodeByHandle(nodeHandle)?.let { megaNode -> + openShareDialog(megaNode) + } + } + } + }.onSuccess { + _state.update { + it.copy(isOpenShareDialogSuccess = true) + } + }.onFailure { + _state.update { + it.copy(isOpenShareDialogSuccess = false) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 40d63801c62..f39e518dbb3 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates -import mega.privacy.android.app.domain.usecase.OpenShareDialog import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.model.OutgoingSharesState import mega.privacy.android.domain.usecase.GetCloudSortOrder @@ -37,7 +36,6 @@ class OutgoingSharesViewModel @Inject constructor( monitorNodeUpdates: MonitorNodeUpdates, private val getFeatureFlagValue: GetFeatureFlagValue, private val getUnverifiedOutgoingShares: GetUnverifiedOutgoingShares, - private val openShareDialog: OpenShareDialog, ) : ViewModel() { /** private UI state */ @@ -203,29 +201,4 @@ class OutgoingSharesViewModel @Inject constructor( it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) } } - - /** - * Calls OpenShareDialog use case to create crypto key for sharing - * - * @param nodeHandle: [MegaNode] handle - */ - fun callOpenShareDialog(nodeHandle: Long) { - kotlin.runCatching { - viewModelScope.launch { - if (!isInvalidHandle(nodeHandle)) { - getNodeByHandle(nodeHandle)?.let { megaNode -> - openShareDialog(megaNode) - } - } - } - }.onSuccess { - _state.update { - it.copy(isOpenShareDialogSuccess = true) - } - }.onFailure { - _state.update { - it.copy(isOpenShareDialogSuccess = false) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index cccd55fa233..b326a7e9987 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -17,7 +17,6 @@ import nz.mega.sdk.MegaNode * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles - * @param isOpenShareDialogSuccess if openShareDialog API call is a success or failure */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -30,7 +29,6 @@ data class OutgoingSharesState( val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodeHandles: List = emptyList(), - val isOpenShareDialogSuccess: Boolean = false, ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt new file mode 100644 index 00000000000..8b715e1556d --- /dev/null +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt @@ -0,0 +1,61 @@ +package test.mega.privacy.android.app.presentation + +import app.cash.turbine.test +import com.google.common.truth.Truth +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import mega.privacy.android.app.domain.usecase.GetNodeByHandle +import mega.privacy.android.app.domain.usecase.OpenShareDialog +import mega.privacy.android.app.modalbottomsheet.NodeOptionsBottomSheetViewModel +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.mock +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@ExperimentalCoroutinesApi +class NodeOptionsBottomSheetViewModelTest { + + private lateinit var underTest: NodeOptionsBottomSheetViewModel + private val getNodeByHandle = mock() + private val openShareDialog = mock() + + @Before + fun setUp() { + Dispatchers.setMain(UnconfinedTestDispatcher()) + underTest = NodeOptionsBottomSheetViewModel(openShareDialog, getNodeByHandle) + } + + @Test + fun `test that initial state is returned`() = runTest { + underTest.state.test { + val initial = awaitItem() + Truth.assertThat(initial.currentNodeHandle).isEqualTo(-1L) + Truth.assertThat(initial.isOpenShareDialogSuccess).isEqualTo(false) + } + } + + @Test + fun `test that open share dialog success result gets updated in state`() = runTest { + underTest.callOpenShareDialog(3829183L) + underTest.state.runCatching { + this.test { + assertTrue(awaitItem().isOpenShareDialogSuccess) + } + } + } + + @Test + fun `test that open share dialog failure result gets updated in state`() = runTest { + underTest.callOpenShareDialog(-1) + underTest.state.runCatching { + this.test { + assertFalse(awaitItem().isOpenShareDialogSuccess) + } + } + } + +} \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index a23deb884d4..a97d38b03e3 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -12,7 +12,6 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode -import mega.privacy.android.app.domain.usecase.OpenShareDialog import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel import mega.privacy.android.domain.entity.ShareData @@ -66,8 +65,6 @@ class OutgoingSharesViewModelTest { onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } - private val openShareDialog = mock() - @Before fun setUp() { Dispatchers.setMain(UnconfinedTestDispatcher()) @@ -84,7 +81,6 @@ class OutgoingSharesViewModelTest { monitorNodeUpdates, getFeatureFlagValue, getUnverifiedOutgoingShares, - openShareDialog, ) } From 40687e6c81b418a651fca6f28033ff3b0a0b01b6 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 17 Feb 2023 19:05:30 +1300 Subject: [PATCH 110/334] MandatoryFingerprintVerification feature flag removed from code --- .../android/app/featuretoggle/AppFeatures.kt | 6 -- .../android/app/main/ManagerActivity.java | 3 +- .../app/main/adapters/MegaNodeAdapter.java | 22 ++--- .../AuthenticityCredentialsViewModel.kt | 12 --- .../model/AuthenticityCredentialsState.kt | 2 - .../view/AuthenticityCredentialsView.kt | 80 +++++++------------ .../presentation/manager/ManagerViewModel.kt | 32 ++------ .../manager/model/ManagerState.kt | 3 +- .../presentation/search/SearchViewModel.kt | 54 ++++++++++--- .../shares/incoming/IncomingSharesFragment.kt | 1 - .../incoming/IncomingSharesViewModel.kt | 16 ---- .../shares/links/LinksFragment.kt | 1 - .../shares/links/LinksViewModel.kt | 23 ------ .../shares/outgoing/OutgoingSharesFragment.kt | 1 - .../outgoing/OutgoingSharesViewModel.kt | 16 ---- .../AuthenticityCredentialsViewModelTest.kt | 11 --- .../AuthenticityCredentialsViewTest.kt | 3 - .../manager/ManagerViewModelTest.kt | 10 +-- .../incoming/IncomingSharesViewModelTest.kt | 10 --- .../shares/links/LinksViewModelTest.kt | 10 --- .../outgoing/OutgoingSharesViewModelTest.kt | 10 --- 21 files changed, 89 insertions(+), 237 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt b/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt index c134804cb88..02698d12616 100644 --- a/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt +++ b/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt @@ -26,12 +26,6 @@ enum class AppFeatures(override val description: String, private val defaultValu */ SetSecureFlag("Sets the secure flag value for MegaApi", false), - /** - * Indicates if the user is cryptographically secure - */ - MandatoryFingerprintVerification("Indicates if mandatory fingerprint verification needs to be done", - false), - /** * User albums toggle */ diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index d48d8f41b58..1a84086c6cb 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -357,6 +357,7 @@ import mega.privacy.android.app.presentation.clouddrive.FileBrowserViewModel; import mega.privacy.android.app.presentation.fileinfo.FileInfoActivity; import mega.privacy.android.app.presentation.fingerprintauth.SecurityUpgradeDialogFragment; +import mega.privacy.android.app.presentation.folderlink.FolderLinkActivity; import mega.privacy.android.app.presentation.inbox.InboxFragment; import mega.privacy.android.app.presentation.inbox.InboxViewModel; import mega.privacy.android.app.presentation.login.LoginActivity; @@ -2509,7 +2510,7 @@ private void collectFlows() { } // Update pending actions badge on bottom navigation menu - if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { + if (managerState.getPendingActionsCount() > 0) { BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); View pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_pending_actions_badge, menuView, false); sharedItemsView.addView(pendingActionsBadge); diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 1101a22b026..9694219e797 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -89,6 +89,7 @@ import mega.privacy.android.app.main.DrawerItem; import mega.privacy.android.app.main.FolderLinkActivity; import mega.privacy.android.app.main.ManagerActivity; +import mega.privacy.android.app.main.contactSharedFolder.ContactSharedFolderFragment; import mega.privacy.android.app.presentation.clouddrive.FileBrowserFragment; import mega.privacy.android.app.presentation.inbox.InboxFragment; import mega.privacy.android.app.presentation.rubbishbin.RubbishBinFragment; @@ -97,13 +98,11 @@ import mega.privacy.android.app.presentation.shares.links.LinksFragment; import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesFragment; import mega.privacy.android.app.utils.ColorUtils; -import mega.privacy.android.app.utils.ContactUtil; import mega.privacy.android.app.utils.MegaNodeUtil; import mega.privacy.android.app.utils.NodeTakenDownDialogListener; import mega.privacy.android.app.utils.ThumbnailUtils; import mega.privacy.android.data.database.DatabaseHandler; import mega.privacy.android.data.model.MegaContactDB; -import mega.privacy.android.domain.entity.ShareData; import mega.privacy.android.domain.entity.SortOrder; import nz.mega.sdk.MegaApiAndroid; import nz.mega.sdk.MegaNode; @@ -149,7 +148,6 @@ public class MegaNodeAdapter extends RecyclerView.Adapter unverifiedIncomingNodeHandles = new HashSet<>(); private final Set unverifiedOutgoingNodeHandles = new HashSet<>(); - private boolean isMandatoryFingerprintVerificationNeeded; public static class ViewHolderBrowser extends RecyclerView.ViewHolder { @@ -242,8 +240,6 @@ else if (type == OUTGOING_SHARES_ADAPTER binding.listModeSwitch.setVisibility(type == LINKS_ADAPTER ? View.GONE : View.VISIBLE); - - setMediaDiscoveryVisibility(binding); } } @@ -434,7 +430,7 @@ void hideMultipleSelect() { } public void selectAll() { - for (int i = 0; i < nodes.size(); i ++) { + for (int i = 0; i < nodes.size(); i++) { selectedItems.put(i, true); notifyItemChanged(i); } @@ -446,7 +442,7 @@ public void clearSelections() { return; } - for (int i = 0; i < nodes.size(); i ++) { + for (int i = 0; i < nodes.size(); i++) { selectedItems.delete(i); notifyItemChanged(i); } @@ -1092,8 +1088,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - boolean hasUnverifiedNodes = isMandatoryFingerprintVerificationNeeded - && !unverifiedIncomingNodeHandles.isEmpty() + boolean hasUnverifiedNodes = !unverifiedIncomingNodeHandles.isEmpty() && unverifiedIncomingNodeHandles.contains(node.getHandle()); if (hasUnverifiedNodes) { showUnverifiedNodeUi(holder, true); @@ -1104,10 +1099,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } } else if (type == OUTGOING_SHARES_ADAPTER) { + holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - boolean hasUnverifiedNodes = isMandatoryFingerprintVerificationNeeded - && !unverifiedOutgoingNodeHandles.isEmpty() + boolean hasUnverifiedNodes = !unverifiedOutgoingNodeHandles.isEmpty() && unverifiedOutgoingNodeHandles.contains(node.getHandle()); if (hasUnverifiedNodes) { showUnverifiedNodeUi(holder, false); @@ -1205,7 +1200,6 @@ public int getItemCount() { @Override public int getItemViewType(int position) { return !nodes.isEmpty() && position == 0 - && type != FOLDER_LINK_ADAPTER && type != CONTACT_SHARED_FOLDER_ADAPTER && type != CONTACT_FILE_ADAPTER ? ITEM_VIEW_TYPE_HEADER @@ -1523,10 +1517,6 @@ public void onCancelClicked() { unHandledItem = -1; } - public void setMandatoryFingerprintVerificationValue(boolean isVerificationNeeded) { - this.isMandatoryFingerprintVerificationNeeded = isVerificationNeeded; - } - /** * Adds unverified incoming nodes to Set * diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt index 9b3726d2933..10afc13c18f 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt @@ -10,13 +10,11 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import mega.privacy.android.app.R -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.contact.authenticitycredendials.model.AuthenticityCredentialsState import mega.privacy.android.app.presentation.extensions.getErrorStringId import mega.privacy.android.domain.exception.MegaException import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetContactCredentials -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetMyCredentials import mega.privacy.android.domain.usecase.MonitorConnectivity import mega.privacy.android.domain.usecase.ResetCredentials @@ -41,7 +39,6 @@ class AuthenticityCredentialsViewModel @Inject constructor( private val verifyCredentials: VerifyCredentials, private val resetCredentials: ResetCredentials, monitorConnectivity: MonitorConnectivity, - private val getFeatureFlagValue: GetFeatureFlagValue, ) : ViewModel() { private val _state = MutableStateFlow(AuthenticityCredentialsState()) @@ -54,7 +51,6 @@ class AuthenticityCredentialsViewModel @Inject constructor( viewModelScope.launch { _state.update { it.copy(myAccountCredentials = getMyCredentials()) } } - getMandatoryFingerPrintVerificationFeatureFlag() } /** @@ -147,12 +143,4 @@ class AuthenticityCredentialsViewModel @Inject constructor( * Updates state after shown error. */ fun errorShown() = _state.update { it.copy(error = null) } - - private fun getMandatoryFingerPrintVerificationFeatureFlag() { - viewModelScope.launch { - _state.update { - it.copy(isMandatoryFingerPrintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt index 66bfe68dd11..86fcca4430a 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt @@ -10,7 +10,6 @@ import mega.privacy.android.domain.entity.contacts.AccountCredentials * @property isVerifyingCredentials True if is already verifying credentials, false otherwise. * @property myAccountCredentials [AccountCredentials.MyAccountCredentials]. * @property error String resource id for showing an error. - * @property isMandatoryFingerPrintVerificationNeeded Feature flag value for Mandatory fingerprint verification */ data class AuthenticityCredentialsState( val contactCredentials: AccountCredentials.ContactCredentials? = null, @@ -18,5 +17,4 @@ data class AuthenticityCredentialsState( val isVerifyingCredentials: Boolean = false, val myAccountCredentials: AccountCredentials.MyAccountCredentials? = null, val error: Int? = null, - val isMandatoryFingerPrintVerificationNeeded: Boolean = false, ) \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt index 3c648f93bdd..bbbe2ee4ad1 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt @@ -132,56 +132,44 @@ fun ContactCredentials( Box(modifier = Modifier .fillMaxSize() .background(color = if (MaterialTheme.colors.isLight) white else dark_grey)) { - val name = if (state.isMandatoryFingerPrintVerificationNeeded) { - state.contactCredentials?.name ?: "" - } else { - stringResource(id = R.string.label_contact_credentials, - state.contactCredentials?.name ?: "") - } Column { - if (state.isMandatoryFingerPrintVerificationNeeded) { - if (isBannerVisible) { - Box(modifier = Modifier - .testTag("CONTACT_VERIFICATION_BANNER_VIEW") - .fillMaxWidth() - .background(color = yellow_100), - contentAlignment = Alignment.CenterEnd) { - Text(modifier = Modifier.padding(start = 24.dp, - top = 14.dp, - bottom = 14.dp, - end = 48.dp), - style = MaterialTheme.typography.body2, - color = black, - text = stringResource(id = R.string.shared_items_verify_credentials_verify_person_banner_label)) + if (isBannerVisible) { + Box(modifier = Modifier + .testTag("CONTACT_VERIFICATION_BANNER_VIEW") + .fillMaxWidth() + .background(color = yellow_100), + contentAlignment = Alignment.CenterEnd) { + Text(modifier = Modifier.padding(start = 24.dp, + top = 14.dp, + bottom = 14.dp, + end = 48.dp), + style = MaterialTheme.typography.body2, + color = black, + text = stringResource(id = R.string.shared_items_verify_credentials_verify_person_banner_label)) - IconButton( - onClick = { isBannerVisible = false }, - modifier = Modifier.padding(start = 310.dp), - enabled = true, - content = { - Icon(painter = painterResource(id = R.drawable.ic_remove_chat_toolbar), - contentDescription = "") - } - ) - } + IconButton( + onClick = { isBannerVisible = false }, + modifier = Modifier.padding(start = 310.dp), + enabled = true, + content = { + Icon(painter = painterResource(id = R.drawable.ic_remove_chat_toolbar), + contentDescription = "") + } + ) } - - Text(modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 24.dp), - style = MaterialTheme.typography.body2, - color = if (MaterialTheme.colors.isLight) grey_alpha_087 else white_alpha_087, - text = if (state.isMandatoryFingerPrintVerificationNeeded) { - // This lint is expected for now. This will get removed later when more conditions are added to decide the text - stringResource(id = R.string.shared_items_verify_credentials_header_incoming) - } else { - stringResource(id = R.string.shared_items_verify_credentials_header_outgoing) - }) } + Text(modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 24.dp), + style = MaterialTheme.typography.body2, + color = if (MaterialTheme.colors.isLight) grey_alpha_087 else white_alpha_087, + text = stringResource(id = R.string.shared_items_verify_credentials_header_outgoing)) + Text(modifier = Modifier.padding(top = 19.dp, start = 72.dp, end = 72.dp), style = MaterialTheme.typography.subtitle1, color = if (MaterialTheme.colors.isLight) grey_alpha_087 else white_alpha_087, - text = name) + text = stringResource(id = R.string.label_contact_credentials, + state.contactCredentials?.name ?: "")) Text(modifier = Modifier.padding(start = 72.dp, end = 72.dp), style = MaterialTheme.typography.body2, @@ -219,14 +207,8 @@ fun ContactCredentials( @Composable fun MyCredentials(state: AuthenticityCredentialsState) { - var credentialsExplanation = stringResource(id = R.string.authenticity_credentials_explanation) - var yourCredentials = stringResource(id = R.string.label_your_credentials) - if (state.isMandatoryFingerPrintVerificationNeeded) { - credentialsExplanation = - stringResource(id = R.string.shared_items_verify_credentials_information) - yourCredentials = - stringResource(id = R.string.shared_items_verify_credentials_my_credentials) - } + val credentialsExplanation = stringResource(id = R.string.authenticity_credentials_explanation) + val yourCredentials = stringResource(id = R.string.label_your_credentials) Text(modifier = Modifier.padding(start = 24.dp, top = 32.dp, end = 24.dp), style = MaterialTheme.typography.body2, diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index da11349e521..055e151e556 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -20,7 +20,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import mega.privacy.android.app.domain.usecase.GetInboxNode import mega.privacy.android.app.domain.usecase.GetPrimarySyncHandle -import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates @@ -56,9 +55,9 @@ import mega.privacy.android.domain.usecase.MonitorContactRequestUpdates import mega.privacy.android.domain.usecase.MonitorMyAvatarFile import mega.privacy.android.domain.usecase.MonitorStorageStateEvent import mega.privacy.android.domain.usecase.SendStatisticsMediaDiscovery +import nz.mega.sdk.MegaEvent import mega.privacy.android.domain.usecase.billing.GetActiveSubscription import mega.privacy.android.domain.usecase.viewtype.MonitorViewType -import nz.mega.sdk.MegaEvent import nz.mega.sdk.MegaNode import nz.mega.sdk.MegaUser import nz.mega.sdk.MegaUserAlert @@ -71,7 +70,6 @@ import javax.inject.Inject * * @param monitorNodeUpdates Monitor global node updates * @param monitorGlobalUpdates Monitor global updates - * @param getRubbishBinChildrenNode Fetch the rubbish bin nodes * @param monitorContactRequestUpdates * @param getInboxNode * @param getNumUnreadUserAlerts @@ -83,12 +81,14 @@ import javax.inject.Inject * @param monitorStorageStateEvent monitor global storage state changes * @param monitorViewType * @param getCloudSortOrder + * @param getFeatureFlagValue + * @param getUnverifiedIncomingShares + * @param getUnverifiedOutgoingShares */ @HiltViewModel class ManagerViewModel @Inject constructor( monitorNodeUpdates: MonitorNodeUpdates, private val monitorGlobalUpdates: MonitorGlobalUpdates, - private val getRubbishBinChildrenNode: GetRubbishBinChildrenNode, monitorContactRequestUpdates: MonitorContactRequestUpdates, private val getInboxNode: GetInboxNode, private val getNumUnreadUserAlerts: GetNumUnreadUserAlerts, @@ -150,7 +150,6 @@ class ManagerViewModel @Inject constructor( ) init { - viewModelScope.launch { monitorNodeUpdates().collect { val nodeList = it.changes.keys.toList() @@ -168,9 +167,8 @@ class ManagerViewModel @Inject constructor( } viewModelScope.launch { - _state.update { - it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) - } + val showSyncSection = getFeatureFlagValue(AppFeatures.AndroidSync) + _state.value = _state.value.copy(showSyncSection = showSyncSection) } viewModelScope.launch { @@ -189,7 +187,7 @@ class ManagerViewModel @Inject constructor( viewModelScope.launch { updateGlobalEvents.collect { megaEvent -> - if (_state.value.isMandatoryFingerprintVerificationNeeded && megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { + if (megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { _state.update { it.copy(shouldAlertUserAboutSecurityUpgrade = true) } @@ -205,12 +203,6 @@ class ManagerViewModel @Inject constructor( private val _updates = monitorGlobalUpdates() .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) - /** - * Monitor global node updates - */ - private val _updateNodes = monitorNodeUpdates() - .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) - /** * Monitor contact requests */ @@ -263,16 +255,6 @@ class ManagerViewModel @Inject constructor( .map { Event(it) } .asLiveData() - /** - * Update Rubbish Nodes when a node update callback happens - */ - val updateRubbishBinNodes: LiveData>> = - _updateNodes - .also { Timber.d("onRubbishNodesUpdate") } - .mapNotNull { getRubbishBinChildrenNode(_state.value.rubbishBinParentHandle) } - .map { Event(it) } - .asLiveData() - /** * On my avatar file changed */ diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index 8dca9970c14..ed40db8756e 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -12,9 +12,9 @@ package mega.privacy.android.app.presentation.manager.model * @param shouldStopCameraUpload camera upload should be stopped or not * @param shouldSendCameraBroadcastEvent broadcast event should be sent or not * @param nodeUpdateReceived one-off event to notify UI that a node update occurred - * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param pendingActionsCount Pending actions count * @param shouldAlertUserAboutSecurityUpgrade Boolean to decide whether to display security upgrade dialog or not + * @param showSyncSection Boolean to show sync section */ data class ManagerState( val rubbishBinParentHandle: Long = -1L, @@ -26,7 +26,6 @@ data class ManagerState( val shouldStopCameraUpload: Boolean = false, val shouldSendCameraBroadcastEvent: Boolean = false, val nodeUpdateReceived: Boolean = false, - val isMandatoryFingerprintVerificationNeeded: Boolean = false, val pendingActionsCount: Int = 0, val shouldAlertUserAboutSecurityUpgrade: Boolean = false, ) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt index b991dcf40cb..52e378a5609 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import mega.privacy.android.app.domain.usecase.GetRootFolder import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.fragments.homepage.Event import mega.privacy.android.app.main.DrawerItem import mega.privacy.android.app.presentation.manager.model.SharesTab @@ -25,12 +24,13 @@ import mega.privacy.android.app.search.usecase.SearchNodesUseCase import mega.privacy.android.app.search.usecase.SearchNodesUseCase.Companion.TYPE_GENERAL import mega.privacy.android.data.mapper.SortOrderIntMapper import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue +import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.RootNodeExists import nz.mega.sdk.MegaApiJava.INVALID_HANDLE import nz.mega.sdk.MegaCancelToken import nz.mega.sdk.MegaNode import timber.log.Timber +import java.util.Stack import javax.inject.Inject /** @@ -40,6 +40,7 @@ import javax.inject.Inject * @param rootNodeExists Check if the root node exists * @param searchNodesUseCase Perform a search request * @param getCloudSortOrder Get the Cloud Sort Order + * @param getSearchParentNodeHandle Get parent node for current node */ @HiltViewModel class SearchViewModel @Inject constructor( @@ -48,7 +49,7 @@ class SearchViewModel @Inject constructor( private val getRootFolder: GetRootFolder, private val searchNodesUseCase: SearchNodesUseCase, private val getCloudSortOrder: GetCloudSortOrder, - private val getFeatureFlagValue: GetFeatureFlagValue, + private val getSearchParentNodeHandle: GetParentNodeHandle, ) : ViewModel() { /** @@ -68,6 +69,11 @@ class SearchViewModel @Inject constructor( */ val stateLiveData = _state.map { Event(it) }.asLiveData() + /** + * Stack to maintain folder navigation clicks + */ + private val lastPositionStack: Stack = Stack() + /** * Monitor global node updates */ @@ -78,10 +84,6 @@ class SearchViewModel @Inject constructor( .map { Event(it) } .asLiveData() - init { - isMandatoryFingerprintRequired() - } - /** * Current search cancel token after a search request has been performed */ @@ -140,10 +142,19 @@ class SearchViewModel @Inject constructor( _state.update { it.copy(searchDepth = it.searchDepth - 1) } } + /** + * Handles Folder item clicked on [SearchFragment] + */ + fun onFolderClicked(handle: Long, lastFirstVisiblePosition: Int) { + setSearchParentHandle(handle) + increaseSearchDepth() + lastPositionStack.push(lastFirstVisiblePosition) + } + /** * Increase by 1 the search depth */ - fun increaseSearchDepth() = viewModelScope.launch { + private fun increaseSearchDepth() = viewModelScope.launch { _state.update { it.copy(searchDepth = it.searchDepth + 1) } } @@ -332,13 +343,30 @@ class SearchViewModel @Inject constructor( fun getOrder() = runBlocking { getCloudSortOrder() } /** - * Gets the feature flag value & updates state + * Handles back click [SearchFragment] */ - private fun isMandatoryFingerprintRequired() { - viewModelScope.launch { - _state.update { - it.copy(isMandatoryFingerPrintVerificationRequired = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) + fun onBackClicked() { + cancelSearch() + val levelSearch = _state.value.searchDepth + if (levelSearch >= 0) { + if (levelSearch > 0) { + viewModelScope.launch { + getSearchParentNodeHandle(_state.value.searchParentHandle)?.let { + setSearchParentHandle(it) + } ?: run { + setSearchParentHandle(-1) + } + } + } else { + setSearchParentHandle(-1) } } } + + /** + * Pop scroll position for previous depth + * + * @return last position saved + */ + fun popLastPositionStack(): Int = lastPositionStack.takeIf { it.isNotEmpty() }?.pop() ?: 0 } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 5a69738d9c8..66ab4aed975 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -250,7 +250,6 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) - adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) adapter?.setUnverifiedIncomingNodeHandles(it.unVerifiedIncomingNodeHandles) updateNodes(it.nodes) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 2d4a7343adb..5f52f9ac7b6 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -11,10 +11,8 @@ import mega.privacy.android.app.domain.usecase.AuthorizeNode import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.incoming.model.IncomingSharesState import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares @@ -36,7 +34,6 @@ class IncomingSharesViewModel @Inject constructor( private val getCloudSortOrder: GetCloudSortOrder, private val getOthersSortOrder: GetOthersSortOrder, monitorNodeUpdates: MonitorNodeUpdates, - private val getFeatureFlagValue: GetFeatureFlagValue, private val getUnverifiedIncomingShares: GetUnverifiedIncomingShares, ) : ViewModel() { @@ -71,10 +68,6 @@ class IncomingSharesViewModel @Inject constructor( } } - viewModelScope.launch { - isMandatoryFingerprintRequired() - } - viewModelScope.launch { val unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder) .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } @@ -208,13 +201,4 @@ class IncomingSharesViewModel @Inject constructor( ?.let { getNodeByHandle(it) == null } ?: true } - - /** - * Gets the feature flag value & updates state - */ - private suspend fun isMandatoryFingerprintRequired() { - _state.update { - it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) - } - } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt index 46dcfbed536..1cc0f9f0e1d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt @@ -234,7 +234,6 @@ class LinksFragment : MegaNodeBaseFragment() { adapter?.isMultipleSelect = false recyclerView?.adapter = adapter - adapter?.setMandatoryFingerprintVerificationValue(viewModel.mandatoryFingerPrintVerificationState.value) } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksViewModel.kt index fbdd3f4fef3..7aaf7a8e727 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksViewModel.kt @@ -10,10 +10,8 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetPublicLinks import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.links.model.LinksState import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetLinksSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import nz.mega.sdk.MegaApiJava @@ -33,7 +31,6 @@ class LinksViewModel @Inject constructor( private val getCloudSortOrder: GetCloudSortOrder, private val getLinksSortOrder: GetLinksSortOrder, monitorNodeUpdates: MonitorNodeUpdates, - private val getFeatureFlagValue: GetFeatureFlagValue, ) : ViewModel() { /** private UI state */ @@ -45,14 +42,6 @@ class LinksViewModel @Inject constructor( /** stack of scroll position for each depth */ private val lastPositionStack: Stack = Stack() - private val _mandatoryFingerPrintVerificationState = MutableStateFlow(false) - - /** - * State for [MandatoryFingerPrintVerification] feature flag value - */ - val mandatoryFingerPrintVerificationState: StateFlow = - _mandatoryFingerPrintVerificationState - init { viewModelScope.launch { refreshNodes()?.let { setNodes(it) } @@ -61,7 +50,6 @@ class LinksViewModel @Inject constructor( refreshNodes()?.let { setNodes(it) } } } - isMandatoryFingerprintRequired() } /** @@ -182,15 +170,4 @@ class LinksViewModel @Inject constructor( ?.let { getNodeByHandle(it) == null } ?: true } - - /** - * Gets the feature flag value & updates state - */ - private fun isMandatoryFingerprintRequired() { - viewModelScope.launch { - _mandatoryFingerPrintVerificationState.update { - getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 4810411a01a..303b83bbf2f 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -233,7 +233,6 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) - adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) adapter?.setUnverifiedOutgoingNodeHandles(it.unVerifiedOutgoingNodeHandles) updateNodes(it.nodes) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index f39e518dbb3..cd932bf01e5 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -10,10 +10,8 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.model.OutgoingSharesState import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares @@ -34,7 +32,6 @@ class OutgoingSharesViewModel @Inject constructor( private val getCloudSortOrder: GetCloudSortOrder, private val getOthersSortOrder: GetOthersSortOrder, monitorNodeUpdates: MonitorNodeUpdates, - private val getFeatureFlagValue: GetFeatureFlagValue, private val getUnverifiedOutgoingShares: GetUnverifiedOutgoingShares, ) : ViewModel() { @@ -56,10 +53,6 @@ class OutgoingSharesViewModel @Inject constructor( } } - viewModelScope.launch { - isMandatoryFingerprintRequired() - } - viewModelScope.launch { val unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder) .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } @@ -192,13 +185,4 @@ class OutgoingSharesViewModel @Inject constructor( ?.let { getNodeByHandle(it) == null } ?: true } - - /** - * Gets the feature flag value & updates state - */ - private suspend fun isMandatoryFingerprintRequired() { - _state.update { - it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) - } - } } \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModelTest.kt index fa428734344..0ff7999f450 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModelTest.kt @@ -14,13 +14,11 @@ import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.R -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsViewModel import mega.privacy.android.domain.entity.contacts.AccountCredentials import mega.privacy.android.domain.exception.MegaException import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetContactCredentials -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetMyCredentials import mega.privacy.android.domain.usecase.MonitorConnectivity import mega.privacy.android.domain.usecase.ResetCredentials @@ -30,7 +28,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule -import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @@ -102,13 +99,6 @@ class AuthenticityCredentialsViewModelTest { private val monitorConnectivity = mock { on { invoke() }.thenReturn(MutableStateFlow(true)) } - private val getFeatureFlagValue = - mock { - onBlocking { - invoke(AppFeatures.MandatoryFingerprintVerification) - }.thenReturn(true) - } - @Before fun setUp() { Dispatchers.setMain(StandardTestDispatcher(scheduler)) @@ -119,7 +109,6 @@ class AuthenticityCredentialsViewModelTest { verifyCredentials = verifyCredentials, resetCredentials = resetCredentials, monitorConnectivity = monitorConnectivity, - getFeatureFlagValue = getFeatureFlagValue, ) } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt index 955db619078..053860cf6c5 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt @@ -149,13 +149,10 @@ class AuthenticityCredentialsViewTest { initComposeRuleContent(AuthenticityCredentialsState( contactCredentials = contactCredentials, myAccountCredentials = AccountCredentials.MyAccountCredentials(myCredentials), - isMandatoryFingerPrintVerificationNeeded = true )) composeTestRule.onNodeWithTag("CONTACT_VERIFICATION_BANNER_VIEW").assertExists() composeTestRule.onNodeWithText(R.string.shared_items_verify_credentials_verify_person_banner_label) .assertExists() - composeTestRule.onNodeWithText(R.string.shared_items_verify_credentials_information) - .assertExists() composeTestRule.onNodeWithText(R.string.authenticity_credentials_label) .assertExists() } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index 2e6ce45c653..f67a2461e34 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -21,7 +21,6 @@ import mega.privacy.android.app.domain.usecase.GetPrimarySyncHandle import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.manager.ManagerViewModel import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.manager.model.TransfersTab @@ -74,14 +73,7 @@ class ManagerViewModelTest { private val checkCameraUpload = mock() private val getCloudSortOrder = mock() private val monitorConnectivity = mock() - - private val getFeatureFlagValue = - mock { - onBlocking { - invoke(AppFeatures.MandatoryFingerprintVerification) - }.thenReturn(true) - } - + private val getFeatureFlagValue = mock() private val getUnverifiedOutgoingShares = mock() private val getUnverifiedIncomingShares = mock() diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index d423fda0313..4819018e207 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.AuthorizeNode import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder @@ -23,7 +22,6 @@ import mega.privacy.android.domain.entity.node.NodeChanges import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares @@ -58,13 +56,6 @@ class IncomingSharesViewModelTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() - private val getFeatureFlagValue = - mock { - onBlocking { - invoke(AppFeatures.MandatoryFingerprintVerification) - }.thenReturn(true) - } - private val getUnverifiedIncomingShares = mock { val shareData = ShareData("user", 8766L, 0, 987654678L, true) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) @@ -90,7 +81,6 @@ class IncomingSharesViewModelTest { getCloudSortOrder, getOtherSortOrder, monitorNodeUpdates, - getFeatureFlagValue, getUnverifiedIncomingShares, ) } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/links/LinksViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/links/LinksViewModelTest.kt index 98f949388cc..b651cf73f27 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/links/LinksViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/links/LinksViewModelTest.kt @@ -12,14 +12,12 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetPublicLinks -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.links.LinksViewModel import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetLinksSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import nz.mega.sdk.MegaNode @@ -51,13 +49,6 @@ class LinksViewModelTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() - private val getFeatureFlagValue = - mock { - onBlocking { - invoke(AppFeatures.MandatoryFingerprintVerification) - }.thenReturn(true) - } - @Before fun setUp() { Dispatchers.setMain(UnconfinedTestDispatcher()) @@ -68,7 +59,6 @@ class LinksViewModelTest { getCloudSortOrder, getLinksSortOrder, monitorNodeUpdates, - getFeatureFlagValue, ) } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index a97d38b03e3..d9f187e04eb 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -12,7 +12,6 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder @@ -20,7 +19,6 @@ import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares @@ -53,13 +51,6 @@ class OutgoingSharesViewModelTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() - private val getFeatureFlagValue = - mock { - onBlocking { - invoke(AppFeatures.MandatoryFingerprintVerification) - }.thenReturn(true) - } - private val getUnverifiedOutgoingShares = mock { val shareData = ShareData("user", 8766L, 0, 987654678L, true) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) @@ -79,7 +70,6 @@ class OutgoingSharesViewModelTest { getCloudSortOrder, getOtherSortOrder, monitorNodeUpdates, - getFeatureFlagValue, getUnverifiedOutgoingShares, ) } From da0935a7691439d30fadf1cae8f0bc5036db676c Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 17 Feb 2023 21:36:43 +1300 Subject: [PATCH 111/334] Unit test case failure fixed --- .../SecurityUpgradeDialogTest.kt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt diff --git a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt new file mode 100644 index 00000000000..1dbe892bf93 --- /dev/null +++ b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt @@ -0,0 +1,46 @@ +package test.mega.privacy.android.app.presentation.fingerprintauth + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.hilt.android.testing.HiltAndroidTest +import mega.privacy.android.app.R +import mega.privacy.android.app.presentation.fingerprintauth.SecurityUpgradeDialogView +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import test.mega.privacy.android.app.onNodeWithText + +@HiltAndroidTest +@RunWith(AndroidJUnit4::class) +class SecurityUpgradeDialogTest { + + @get:Rule + val composeTestRule = createComposeRule() + + private fun initComposeRule() { + composeTestRule.setContent { + SecurityUpgradeDialogView(folderNames = listOf("folder name 1 ", + "folder name 2 ", + "folder name 3 "), + onOkClick = { }, + onCancelClick = {}) + } + } + + @Test + fun test_that_imageview_resource_is_as_expected() { + initComposeRule() + composeTestRule.run { + onNodeWithTag("HeaderImage").assertIsDisplayed() + onNodeWithText(R.string.shared_items_security_upgrade_dialog_title).assertIsDisplayed() + onNodeWithText(R.string.shared_items_security_upgrade_dialog_content).assertIsDisplayed() + onNodeWithTag("SharedNodeInfo").assertIsDisplayed() + onNodeWithText(R.string.general_ok).assertIsDisplayed() + onNodeWithText(R.string.button_cancel).assertIsDisplayed() + } + } +} \ No newline at end of file From 355453a4aa85312eef4880be6af0721d1bf25eda Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 15 Feb 2023 20:13:31 +1300 Subject: [PATCH 112/334] AND-15405 NodeOptionsBottomSheetViewModel scoped to fragment --- .../NodeOptionsBottomSheetDialogFragment.java | 13 ++++++++----- .../modalbottomsheet/NodeOptionsBottomSheetState.kt | 2 +- .../NodeOptionsBottomSheetViewModel.kt | 11 +++++++++++ .../NodeOptionsBottomSheetViewModelTest.kt | 6 +++--- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 23fcb7d7e88..b5fd50272c1 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -193,7 +193,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class); incomingSharesViewModel = new ViewModelProvider(requireActivity()).get(IncomingSharesViewModel.class); outgoingSharesViewModel = new ViewModelProvider(requireActivity()).get(OutgoingSharesViewModel.class); - nodeOptionsBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(NodeOptionsBottomSheetViewModel.class); + nodeOptionsBottomSheetViewModel = new ViewModelProvider(this).get(NodeOptionsBottomSheetViewModel.class); } @Nullable @@ -742,11 +742,14 @@ && isNodeUnverified(state.getUnVerifiedOutgoingNodeHandles())) { } ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), nodeOptionsBottomSheetViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (state.isOpenShareDialogSuccess()) { - showShareFolderOptions(); - } else { - showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); + if(state.isOpenShareDialogSuccess() != null) { + if (state.isOpenShareDialogSuccess()) { + showShareFolderOptions(); + } else { + showSnackbar(requireActivity(), getString(R.string.general_something_went_wrong_error)); + } } + nodeOptionsBottomSheetViewModel.resetIsOpenShareDialogSuccess(); return Unit.INSTANCE; }); } diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt index b9873472936..2875eee872d 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt @@ -8,5 +8,5 @@ package mega.privacy.android.app.modalbottomsheet */ data class NodeOptionsBottomSheetState( val currentNodeHandle: Long = -1L, - val isOpenShareDialogSuccess: Boolean = false, + val isOpenShareDialogSuccess: Boolean? = null, ) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt index cf70e2ad2ac..3b483b693d8 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt @@ -69,4 +69,15 @@ class NodeOptionsBottomSheetViewModel @Inject constructor( } } } + + /** + * Change the value of isOpenShareDialogSuccess to false after it is consumed. + */ + fun resetIsOpenShareDialogSuccess() { + viewModelScope.launch { + _state.update { + it.copy(isOpenShareDialogSuccess = null) + } + } + } } \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt index 8b715e1556d..02153c8769a 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt @@ -34,7 +34,7 @@ class NodeOptionsBottomSheetViewModelTest { underTest.state.test { val initial = awaitItem() Truth.assertThat(initial.currentNodeHandle).isEqualTo(-1L) - Truth.assertThat(initial.isOpenShareDialogSuccess).isEqualTo(false) + Truth.assertThat(initial.isOpenShareDialogSuccess).isEqualTo(null) } } @@ -43,7 +43,7 @@ class NodeOptionsBottomSheetViewModelTest { underTest.callOpenShareDialog(3829183L) underTest.state.runCatching { this.test { - assertTrue(awaitItem().isOpenShareDialogSuccess) + awaitItem().isOpenShareDialogSuccess?.let { assertTrue(it) } } } } @@ -53,7 +53,7 @@ class NodeOptionsBottomSheetViewModelTest { underTest.callOpenShareDialog(-1) underTest.state.runCatching { this.test { - assertFalse(awaitItem().isOpenShareDialogSuccess) + awaitItem().isOpenShareDialogSuccess?.let { assertFalse(it) } } } } From ebba4e9ec2048d7463b56a48d6579320fe622d3d Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 20 Feb 2023 14:45:49 +0530 Subject: [PATCH 113/334] Update Prebuilt ask version , Remove unnecessary changes --- .../privacy/android/app/di/GetNodeModule.kt | 15 +------ .../android/app/main/ManagerActivity.java | 42 +------------------ .../manager/model/ManagerState.kt | 3 +- .../presentation/search/SearchViewModel.kt | 2 +- .../shares/incoming/IncomingSharesFragment.kt | 1 - build.gradle | 2 +- 6 files changed, 6 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt index 5eef302b045..afb6fd4c70a 100644 --- a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt @@ -11,16 +11,15 @@ import mega.privacy.android.app.domain.usecase.CopyNode import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.OpenShareDialog -import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.app.namecollision.usecase.CheckNameCollisionUseCase import mega.privacy.android.app.usecase.MoveNodeUseCase +import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares -import mega.privacy.android.domain.usecase.SetSecureFlag +import mega.privacy.android.domain.usecase.UpgradeSecurity import mega.privacy.android.domain.usecase.filenode.CopyNodeByHandle import mega.privacy.android.domain.usecase.filenode.CopyNodeByHandleChangingName import mega.privacy.android.domain.usecase.filenode.MoveNodeByHandle -import mega.privacy.android.domain.usecase.UpgradeSecurity /** * Get node module @@ -132,16 +131,6 @@ abstract class GetNodeModule { moveNodeUseCase.move(nodeToCopy.longValue, newNodeParent.longValue).await() } - /** - * Provides [SetSecureFlag] implementation - * - * @param megaNodeRepository [MegaNodeRepository] - * @return [SetSecureFlag] - */ - @Provides - fun provideSetSecureFlag(megaNodeRepository: MegaNodeRepository): SetSecureFlag = - SetSecureFlag(megaNodeRepository::setSecureFlag) - /** * Provides [OpenShareDialog] implementation * diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 1a84086c6cb..148fca341fd 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -16,15 +16,12 @@ import static mega.privacy.android.app.constants.BroadcastConstants.INVALID_ACTION; import static mega.privacy.android.app.constants.EventConstants.EVENT_CALL_ON_HOLD_CHANGE; import static mega.privacy.android.app.constants.EventConstants.EVENT_CALL_STATUS_CHANGE; -import static mega.privacy.android.app.constants.EventConstants.EVENT_FAILED_TRANSFERS; import static mega.privacy.android.app.constants.EventConstants.EVENT_FINISH_ACTIVITY; import static mega.privacy.android.app.constants.EventConstants.EVENT_REFRESH; import static mega.privacy.android.app.constants.EventConstants.EVENT_REFRESH_PHONE_NUMBER; import static mega.privacy.android.app.constants.EventConstants.EVENT_SESSION_ON_HOLD_CHANGE; -import static mega.privacy.android.app.constants.EventConstants.EVENT_TRANSFER_OVER_QUOTA; import static mega.privacy.android.app.constants.EventConstants.EVENT_UPDATE_VIEW_MODE; import static mega.privacy.android.app.constants.EventConstants.EVENT_USER_EMAIL_UPDATED; -import static mega.privacy.android.app.constants.EventConstants.EVENT_USER_NAME_UPDATED; import static mega.privacy.android.app.constants.IntentConstants.ACTION_OPEN_ACHIEVEMENTS; import static mega.privacy.android.app.constants.IntentConstants.EXTRA_ACCOUNT_TYPE; import static mega.privacy.android.app.constants.IntentConstants.EXTRA_ASK_PERMISSIONS; @@ -96,7 +93,6 @@ import static mega.privacy.android.app.utils.JobUtil.fireCameraUploadJob; import static mega.privacy.android.app.utils.JobUtil.fireCancelCameraUploadJob; import static mega.privacy.android.app.utils.JobUtil.fireStopCameraUploadJob; -import static mega.privacy.android.app.utils.JobUtil.stopCameraUploadSyncHeartbeatWorkers; import static mega.privacy.android.app.utils.MegaApiUtils.calculateDeepBrowserTreeIncoming; import static mega.privacy.android.app.utils.MegaNodeDialogUtil.ACTION_BACKUP_FAB; import static mega.privacy.android.app.utils.MegaNodeDialogUtil.ACTION_BACKUP_SHARE_FOLDER; @@ -120,7 +116,6 @@ import static mega.privacy.android.app.utils.OfflineUtils.saveOffline; import static mega.privacy.android.app.utils.StringResourcesUtils.getQuantityString; import static mega.privacy.android.app.utils.TextUtil.isTextEmpty; -import static mega.privacy.android.app.utils.TimeUtils.getHumanizedTime; import static mega.privacy.android.app.utils.UploadUtil.chooseFiles; import static mega.privacy.android.app.utils.UploadUtil.chooseFolder; import static mega.privacy.android.app.utils.UploadUtil.getFolder; @@ -218,7 +213,6 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SearchView; import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; @@ -298,7 +292,6 @@ import mega.privacy.android.app.fragments.homepage.documents.DocumentsFragment; import mega.privacy.android.app.fragments.homepage.main.HomepageFragment; import mega.privacy.android.app.fragments.homepage.main.HomepageFragmentDirections; -import mega.privacy.android.app.fragments.managerFragments.cu.CustomHideBottomViewOnScrollBehaviour; import mega.privacy.android.app.fragments.offline.OfflineFragment; import mega.privacy.android.app.fragments.recent.RecentsBucketFragment; import mega.privacy.android.app.fragments.settingsFragments.cookie.CookieDialogHandler; @@ -324,7 +317,6 @@ import mega.privacy.android.app.main.listeners.CreateGroupChatWithPublicLink; import mega.privacy.android.app.main.listeners.FabButtonListener; import mega.privacy.android.app.main.managerSections.CompletedTransfersFragment; -import mega.privacy.android.app.main.managerSections.NotificationsFragment; import mega.privacy.android.app.main.managerSections.TransfersFragment; import mega.privacy.android.app.main.managerSections.TurnOnNotificationsFragment; import mega.privacy.android.app.main.megachat.BadgeDrawerArrowDrawable; @@ -355,12 +347,9 @@ import mega.privacy.android.app.objects.PasscodeManagement; import mega.privacy.android.app.presentation.clouddrive.FileBrowserFragment; import mega.privacy.android.app.presentation.clouddrive.FileBrowserViewModel; -import mega.privacy.android.app.presentation.fileinfo.FileInfoActivity; import mega.privacy.android.app.presentation.fingerprintauth.SecurityUpgradeDialogFragment; -import mega.privacy.android.app.presentation.folderlink.FolderLinkActivity; import mega.privacy.android.app.presentation.inbox.InboxFragment; import mega.privacy.android.app.presentation.inbox.InboxViewModel; -import mega.privacy.android.app.presentation.login.LoginActivity; import mega.privacy.android.app.presentation.manager.ManagerViewModel; import mega.privacy.android.app.presentation.manager.UnreadUserAlertsCheckType; import mega.privacy.android.app.presentation.manager.UserInfoViewModel; @@ -374,7 +363,6 @@ import mega.privacy.android.app.presentation.photos.albums.AlbumDynamicContentFragment; import mega.privacy.android.app.presentation.photos.mediadiscovery.MediaDiscoveryFragment; import mega.privacy.android.app.presentation.photos.timeline.photosfilter.PhotosFilterFragment; -import mega.privacy.android.app.presentation.qrcode.scan.ScanCodeFragment; import mega.privacy.android.app.presentation.rubbishbin.RubbishBinFragment; import mega.privacy.android.app.presentation.rubbishbin.RubbishBinViewModel; import mega.privacy.android.app.presentation.search.SearchFragment; @@ -399,7 +387,6 @@ import mega.privacy.android.app.upgradeAccount.UpgradeAccountActivity; import mega.privacy.android.app.usecase.CopyNodeUseCase; import mega.privacy.android.app.usecase.DownloadNodeUseCase; -import mega.privacy.android.app.usecase.GetNodeUseCase; import mega.privacy.android.app.usecase.MoveNodeUseCase; import mega.privacy.android.app.usecase.RemoveNodeUseCase; import mega.privacy.android.app.usecase.UploadUseCase; @@ -439,7 +426,6 @@ import mega.privacy.android.domain.entity.contacts.ContactRequest; import mega.privacy.android.domain.entity.contacts.ContactRequestStatus; import mega.privacy.android.domain.entity.preference.ViewType; -import mega.privacy.android.domain.entity.user.UserCredentials; import mega.privacy.android.domain.qualifier.ApplicationScope; import nz.mega.documentscanner.DocumentScannerActivity; import nz.mega.sdk.MegaAccountDetails; @@ -545,8 +531,6 @@ public class ManagerActivity extends TransfersManagementActivity @Inject RemoveNodeUseCase removeNodeUseCase; @Inject - GetNodeUseCase getNodeUseCase; - @Inject GetChatChangesUseCase getChatChangesUseCase; @Inject DownloadNodeUseCase downloadNodeUseCase; @@ -1449,12 +1433,6 @@ protected void onCreate(Bundle savedInstanceState) { LiveEventBus.get(EVENT_REFRESH_PHONE_NUMBER, Boolean.class) .observeForever(refreshAddPhoneNumberButtonObserver); - LiveEventBus.get(EVENT_FAILED_TRANSFERS, Boolean.class).observe(this, failed -> { - if (drawerItem == DrawerItem.TRANSFERS && getTabItemTransfers() == TransfersTab.COMPLETED_TAB) { - retryTransfers.setVisible(failed); - } - }); - registerReceiver(transferFinishReceiver, new IntentFilter(BROADCAST_ACTION_TRANSFER_FINISH)); LiveEventBus.get(EVENT_CALL_STATUS_CHANGE, MegaChatCall.class).observe(this, callStatusObserver); @@ -1534,28 +1512,18 @@ protected void onCreate(Bundle savedInstanceState) { prefs = dbH.getPreferences(); if (prefs == null) { firstTimeAfterInstallation = true; - isList = true; } else { if (prefs.getFirstTime() == null) { firstTimeAfterInstallation = true; } else { firstTimeAfterInstallation = Boolean.parseBoolean(prefs.getFirstTime()); } - if (prefs.getPreferredViewList() == null) { - isList = true; - } else { - isList = Boolean.parseBoolean(prefs.getPreferredViewList()); - } } if (firstTimeAfterInstallation) { setStartScreenTimeStamp(this); } - Timber.d("Preferred View List: %s", isList); - - LiveEventBus.get(EVENT_LIST_GRID_CHANGE, Boolean.class).post(isList); - handler = new Handler(); Timber.d("Set view"); @@ -2479,7 +2447,7 @@ public void onPageScrollStateChanged(int state) { } else { Timber.d("Backup warning dialog is not show"); } - } + } /** * collecting Flows from ViewModels @@ -9867,14 +9835,6 @@ public boolean isList() { return isList; } - public void setList(boolean isList) { - this.isList = isList; - } - - public boolean isListCameraUploads() { - return false; - } - public boolean getFirstLogin() { return viewModel.getState().getValue().isFirstLogin(); } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index ed40db8756e..8311d327d9c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -3,7 +3,6 @@ package mega.privacy.android.app.presentation.manager.model /** * Manager UI state * - * @param rubbishBinParentHandle current rubbish bin parent handle * @param isFirstNavigationLevel true if the navigation level is the first level * @param sharesTab current tab in shares screen * @param transfersTab current tab in transfers screen @@ -17,7 +16,6 @@ package mega.privacy.android.app.presentation.manager.model * @param showSyncSection Boolean to show sync section */ data class ManagerState( - val rubbishBinParentHandle: Long = -1L, val isFirstNavigationLevel: Boolean = true, val sharesTab: SharesTab = SharesTab.INCOMING_TAB, val transfersTab: TransfersTab = TransfersTab.NONE, @@ -28,4 +26,5 @@ data class ManagerState( val nodeUpdateReceived: Boolean = false, val pendingActionsCount: Int = 0, val shouldAlertUserAboutSecurityUpgrade: Boolean = false, + val showSyncSection: Boolean = false ) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt index 52e378a5609..22581ec3e33 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt @@ -154,7 +154,7 @@ class SearchViewModel @Inject constructor( /** * Increase by 1 the search depth */ - private fun increaseSearchDepth() = viewModelScope.launch { + fun increaseSearchDepth() = viewModelScope.launch { _state.update { it.copy(searchDepth = it.searchDepth + 1) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 66ab4aed975..28a3af32822 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -68,7 +68,6 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { else getGridView(inflater, container) initAdapter() - observe() selectNewlyAddedNodes() return view diff --git a/build.gradle b/build.gradle index 0d793937bd3..67dc91dc837 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230209.083412-rel' + megaSdkVersion = '20230216.073343-dev' // App dependencies accompanistLayoutVersion = '0.24.13-rc' From 47444e110d4df81495091b7c3b8c0266fa3d9137 Mon Sep 17 00:00:00 2001 From: Nikhil Nagori Date: Mon, 20 Feb 2023 22:33:56 +1300 Subject: [PATCH 114/334] AND-13439 Add thumbnail view mode for folder link --- .../java/mega/privacy/android/app/main/FolderLinkActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/FolderLinkActivity.java b/app/src/main/java/mega/privacy/android/app/main/FolderLinkActivity.java index 44cdd8498a1..9166332142e 100644 --- a/app/src/main/java/mega/privacy/android/app/main/FolderLinkActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/FolderLinkActivity.java @@ -1352,7 +1352,7 @@ public void itemClick(int position) { startActivity(intent); overridePendingTransition(0, 0); } else if (MimeTypeList.typeForName(node.getName()).isVideoReproducible() || MimeTypeList.typeForName(node.getName()).isAudio()) { - MegaNode file = nodes.get(position); + MegaNode file = node; String mimeType = MimeTypeList.typeForName(file.getName()).getType(); Timber.d("FILE HANDLE: %s", file.getHandle()); From 39eece88f4f8c1cdbdfde060d00ac3ffbc7fb64d Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 20 Feb 2023 16:26:02 +0530 Subject: [PATCH 115/334] Remove un-necessary changes --- .../presentation/search/SearchViewModel.kt | 1 - .../manager/ManagerViewModelTest.kt | 48 ------------------- .../SecurityUpgradeDialogTest.kt | 2 - .../android/data/facade/MegaChatApiFacade.kt | 12 +++-- .../data/model/ScheduledMeetingUpdate.kt | 6 ++- .../data/repository/MegaNodeRepository.kt | 7 +++ .../data/repository/MegaNodeRepositoryImpl.kt | 43 +++++++++++++++-- 7 files changed, 59 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt index 22581ec3e33..66e685b4f8c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt @@ -22,7 +22,6 @@ import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.search.model.SearchState import mega.privacy.android.app.search.usecase.SearchNodesUseCase import mega.privacy.android.app.search.usecase.SearchNodesUseCase.Companion.TYPE_GENERAL -import mega.privacy.android.data.mapper.SortOrderIntMapper import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.RootNodeExists diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index f67a2461e34..15e132fd9ea 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -18,7 +18,6 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetInboxNode import mega.privacy.android.app.domain.usecase.GetPrimarySyncHandle -import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates import mega.privacy.android.app.presentation.manager.ManagerViewModel @@ -58,7 +57,6 @@ class ManagerViewModelTest { private val monitorGlobalUpdates = mock() private val monitorNodeUpdates = FakeMonitorUpdates() - private val getRubbishBinNodeByHandle = mock() private val getNumUnreadUserAlerts = mock() private val hasInboxChildren = mock() private val monitorContactRequestUpdates = mock() @@ -93,7 +91,6 @@ class ManagerViewModelTest { underTest = ManagerViewModel( monitorNodeUpdates = monitorNodeUpdates, monitorGlobalUpdates = monitorGlobalUpdates, - getRubbishBinChildrenNode = getRubbishBinNodeByHandle, monitorContactRequestUpdates = monitorContactRequestUpdates, getNumUnreadUserAlerts = getNumUnreadUserAlerts, hasInboxChildren = hasInboxChildren, @@ -142,7 +139,6 @@ class ManagerViewModelTest { setUnderTest() underTest.state.test { val initial = awaitItem() - assertThat(initial.rubbishBinParentHandle).isEqualTo(-1L) assertThat(initial.isFirstNavigationLevel).isTrue() assertThat(initial.sharesTab).isEqualTo(SharesTab.INCOMING_TAB) assertThat(initial.transfersTab).isEqualTo(TransfersTab.NONE) @@ -153,19 +149,6 @@ class ManagerViewModelTest { } } - @Test - fun `test that rubbish bin parent handle is updated if new value provided`() = runTest { - setUnderTest() - - underTest.state.map { it.rubbishBinParentHandle }.distinctUntilChanged() - .test { - val newValue = 123456789L - assertThat(awaitItem()).isEqualTo(-1L) - underTest.setRubbishBinParentHandle(newValue) - assertThat(awaitItem()).isEqualTo(newValue) - } - } - @Test fun `test that is first navigation level is updated if new value provided`() = runTest { setUnderTest() @@ -229,37 +212,6 @@ class ManagerViewModelTest { underTest.updateContactsRequests.test().assertNoValue() } - @Test - fun `test that rubbish bin node updates live data is set when node updates triggered from use case`() = - runTest { - whenever(getRubbishBinNodeByHandle(any())).thenReturn(listOf(mock(), mock())) - - setUnderTest() - - runCatching { - val result = - underTest.updateRubbishBinNodes.test().awaitValue(50, TimeUnit.MILLISECONDS) - monitorNodeUpdates.emit(listOf(mock())) - result - }.onSuccess { result -> - result.assertValue { it.getContentIfNotHandled()?.size == 2 } - } - } - - @Test - fun `test that rubbish bin node updates live data is not set when get rubbish bin node returns a null list`() = - runTest { - whenever(getRubbishBinNodeByHandle(any())).thenReturn(null) - - setUnderTest() - - runCatching { - underTest.updateRubbishBinNodes.test().awaitValue(50, TimeUnit.MILLISECONDS) - }.onSuccess { result -> - result.assertNoValue() - } - } - @Test fun `test that user updates live data is set when user updates triggered from use case`() = runTest { diff --git a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt index 1dbe892bf93..73a51faf9b6 100644 --- a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt +++ b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt @@ -2,9 +2,7 @@ package test.mega.privacy.android.app.presentation.fingerprintauth import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.test.onNodeWithText import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.hilt.android.testing.HiltAndroidTest import mega.privacy.android.app.R diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaChatApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaChatApiFacade.kt index 238a52e43e6..7adaaf2c3bd 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaChatApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaChatApiFacade.kt @@ -280,13 +280,19 @@ internal class MegaChatApiFacade @Inject constructor( trySend(ScheduledMeetingUpdate.OnChatSchedMeetingUpdate(scheduledMeeting)) } - override fun onSchedMeetingOccurrencesUpdate(api: MegaChatApiJava?, chatId: Long) { - trySend(ScheduledMeetingUpdate.OnSchedMeetingOccurrencesUpdate(chatId)) + override fun onSchedMeetingOccurrencesUpdate( + api: MegaChatApiJava?, + chatId: Long, + append: Boolean, + ) { + trySend(ScheduledMeetingUpdate.OnSchedMeetingOccurrencesUpdate(chatId, append)) } } chatApi.addSchedMeetingListener(listener) - awaitClose { chatApi.removeSchedMeetingListener(listener) } + awaitClose { + chatApi.removeSchedMeetingListener(listener) + } }.shareIn(sharingScope, SharingStarted.WhileSubscribed()) override fun getAllScheduledMeetings(): List? = diff --git a/data/src/main/java/mega/privacy/android/data/model/ScheduledMeetingUpdate.kt b/data/src/main/java/mega/privacy/android/data/model/ScheduledMeetingUpdate.kt index 4b61469c785..11a03a04171 100644 --- a/data/src/main/java/mega/privacy/android/data/model/ScheduledMeetingUpdate.kt +++ b/data/src/main/java/mega/privacy/android/data/model/ScheduledMeetingUpdate.kt @@ -20,7 +20,9 @@ sealed class ScheduledMeetingUpdate { /** * On chat scheduled meeting occurrences item update. * - * @property chatId + * @property chatId Chat id + * @property append If append is true, new occurrences has been received from API (no need to discard current ones) */ - data class OnSchedMeetingOccurrencesUpdate(val chatId: Long) : ScheduledMeetingUpdate() + data class OnSchedMeetingOccurrencesUpdate(val chatId: Long, val append: Boolean) : + ScheduledMeetingUpdate() } diff --git a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt index 06e25782ead..cedb75f6c51 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt @@ -259,4 +259,11 @@ interface MegaNodeRepository { * Update cryptographic security */ suspend fun upgradeSecurity() + + /** + * Sets the secure share flag to true or false + * + * @param enable : Boolean + */ + suspend fun setSecureFlag(enable: Boolean) } \ No newline at end of file diff --git a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepositoryImpl.kt b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepositoryImpl.kt index 663e326946b..26e28937e3f 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepositoryImpl.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepositoryImpl.kt @@ -3,8 +3,10 @@ package mega.privacy.android.data.repository import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import mega.privacy.android.data.extensions.failWithError +import mega.privacy.android.data.extensions.getRequestListener import mega.privacy.android.data.gateway.CacheFolderGateway import mega.privacy.android.data.gateway.FileGateway import mega.privacy.android.data.gateway.MegaLocalStorageGateway @@ -21,6 +23,7 @@ import mega.privacy.android.data.mapper.NodeMapper import mega.privacy.android.data.mapper.OfflineNodeInformationMapper import mega.privacy.android.data.mapper.SortOrderIntMapper import mega.privacy.android.domain.entity.FolderVersionInfo +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.exception.MegaException @@ -255,10 +258,42 @@ internal class MegaNodeRepositoryImpl @Inject constructor( megaExceptionMapper(megaApiGateway.checkAccessErrorExtended(node, level)) } + override suspend fun getUnverifiedIncomingShares(order: SortOrder): List = + withContext(ioDispatcher) { + megaApiGateway.getUnverifiedIncomingShares(sortOrderIntMapper(order)).map { + megaShareMapper(it) + } + } + + override suspend fun getUnverifiedOutgoingShares(order: SortOrder): List = + withContext(ioDispatcher) { + megaApiGateway.getUnverifiedOutgoingShares(sortOrderIntMapper(order)).map { + megaShareMapper(it) + } + } + - override suspend fun getUnVerifiedInComingShares(): Int = 3 - //// TODO Please keep this hardcoded for now. Full functionality will be added after SDK changes are available + override suspend fun openShareDialog(megaNode: MegaNode) = withContext(ioDispatcher) { + suspendCancellableCoroutine { continuation -> + val listener = continuation.getRequestListener { return@getRequestListener } + megaApiGateway.openShareDialog(megaNode, listener) + continuation.invokeOnCancellation { + megaApiGateway.removeRequestListener(listener) + } + } + } + + override suspend fun upgradeSecurity() = withContext(ioDispatcher) { + suspendCancellableCoroutine { continuation -> + val listener = continuation.getRequestListener { return@getRequestListener } + megaApiGateway.upgradeSecurity(listener) + continuation.invokeOnCancellation { + megaApiGateway.removeRequestListener(listener) + } + } + } - override suspend fun getUnverifiedOutgoingShares(): Int = 5 - //// TODO Please keep this hardcoded for now. Full functionality will be added after SDK changes are available + override suspend fun setSecureFlag(enable: Boolean) = withContext(ioDispatcher) { + megaApiGateway.setSecureFlag(enable) + } } From aacab2c3b699ca17c201cabb2abff2f04e4b6eec Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 20 Feb 2023 19:34:03 +0530 Subject: [PATCH 116/334] Pre-built version updated --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 67dc91dc837..dac68ba6b5c 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230216.073343-dev' + megaSdkVersion = '20230220.134000-rel' // App dependencies accompanistLayoutVersion = '0.24.13-rc' From 1c37a8cd89fc4366f3b0061bab64bb050b7c3edb Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 20 Feb 2023 19:50:15 +0530 Subject: [PATCH 117/334] appVersion changed from 7.4 to 7.5 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index dac68ba6b5c..0cb83733a4e 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ task clean(type: Delete) { // Define versions in a single place ext { // App - appVersion = "7.4" + appVersion = "7.5" // Sdk and tools compileSdkVersion = 33 From 7edea9550db0c89334465f80476fca328f73c92c Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 21 Feb 2023 22:41:24 +1300 Subject: [PATCH 118/334] AND-15731 Fix NPE by calling updateGlobalEvents after initialisation --- .../android/app/main/ManagerActivity.java | 1 + .../presentation/manager/ManagerViewModel.kt | 25 +++++++++++-------- .../manager/ManagerViewModelTest.kt | 1 + 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 148fca341fd..f28d2a63b9c 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -1291,6 +1291,7 @@ protected void onCreate(Bundle savedInstanceState) { rubbishBinViewModel = new ViewModelProvider(this).get(RubbishBinViewModel.class); searchViewModel = new ViewModelProvider(this).get(SearchViewModel.class); userInfoViewModel = new ViewModelProvider(this).get(UserInfoViewModel.class); + viewModel.monitorGlobalEventUpgradeForUpgradeSecurity(); viewModel.getUpdateUsers().observe(this, new EventObserver<>(users -> { updateUsers(users); diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index 055e151e556..bc957b922ad 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -184,16 +184,6 @@ class ManagerViewModel @Inject constructor( it.copy(pendingActionsCount = _state.value.pendingActionsCount + outgoingShares) } } - - viewModelScope.launch { - updateGlobalEvents.collect { megaEvent -> - if (megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { - _state.update { - it.copy(shouldAlertUserAboutSecurityUpgrade = true) - } - } - } - } } /** @@ -452,4 +442,19 @@ class ManagerViewModel @Inject constructor( * Active subscription in local cache */ val activeSubscription: MegaPurchase? get() = getActiveSubscription() + + /** + * Check global events updates for [MegaEvent.EVENT_UPGRADE_SECURITY] + */ + fun monitorGlobalEventUpgradeForUpgradeSecurity() { + viewModelScope.launch { + updateGlobalEvents.collect { megaEvent -> + if (megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { + _state.update { + it.copy(shouldAlertUserAboutSecurityUpgrade = true) + } + } + } + } + } } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index 15e132fd9ea..fb515a8d403 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -146,6 +146,7 @@ class ManagerViewModelTest { assertThat(initial.shouldSendCameraBroadcastEvent).isFalse() assertThat(initial.shouldStopCameraUpload).isFalse() assertThat(initial.nodeUpdateReceived).isFalse() + assertThat(initial.shouldAlertUserAboutSecurityUpgrade).isFalse() } } From 25d3f24b70f8072af9bb05c6b1f8a3aad5aa94ef Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 22 Feb 2023 08:50:04 +1300 Subject: [PATCH 119/334] AND-15735 - Display contact verification banner on contact verification screen if node is incoming --- .../android/app/main/ManagerActivity.java | 25 +++++++++++++------ .../NodeOptionsBottomSheetDialogFragment.java | 7 +++--- .../AuthenticityCredentialsActivity.kt | 4 +++ .../AuthenticityCredentialsViewModel.kt | 11 ++++++++ .../model/AuthenticityCredentialsState.kt | 2 ++ .../view/AuthenticityCredentialsView.kt | 2 +- .../SecurityUpgradeDialogFragment.kt | 4 +-- .../SecurityUpgradeDialogView.kt | 2 +- .../recentactions/RecentActionsFragment.kt | 18 +++++++++---- .../shares/incoming/IncomingSharesFragment.kt | 6 +++++ .../incoming/model/IncomingSharesState.kt | 2 -- .../outgoing/model/OutgoingSharesState.kt | 2 -- .../privacy/android/app/utils/Constants.java | 1 + .../AuthenticityCredentialsViewTest.kt | 3 ++- 14 files changed, 63 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index f28d2a63b9c..210a8230d02 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -848,6 +848,8 @@ public void onChanged(Boolean aBoolean) { public MegaNode viewInFolderNode; + private ArrayList outgoingFolderNames = new ArrayList<>(); + /** * Broadcast to update the completed transfers tab. */ @@ -2460,9 +2462,20 @@ private void collectFlows() { }); ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { - if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { - replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); + ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { + outgoingFolderNames.clear(); + for (int i = 0; i < outgoingSharesState.getNodes().size(); i++) { + outgoingFolderNames.add(outgoingSharesState.getNodes().get(i).getName()); + } + SecurityUpgradeDialogFragment dialog = SecurityUpgradeDialogFragment.Companion.newInstance(); + Bundle bundle = new Bundle(); + bundle.putStringArrayList("nodeNames", outgoingFolderNames); + dialog.setArguments(bundle); + dialog.show(getSupportFragmentManager(), SecurityUpgradeDialogFragment.TAG); + + return Unit.INSTANCE; + }); } updateInboxSectionVisibility(managerState.getHasInboxChildren()); @@ -2512,16 +2525,12 @@ private void collectFlows() { }); ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { - if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { - addUnverifiedIncomingCountBadge(incomingSharesState.getUnverifiedIncomingShares().size()); - } + addUnverifiedIncomingCountBadge(incomingSharesState.getUnverifiedIncomingShares().size()); return Unit.INSTANCE; }); ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { - if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { - addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnverifiedOutgoingShares().size()); - } + addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnverifiedOutgoingShares().size()); return Unit.INSTANCE; }); } diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index b5fd50272c1..de580d2e2d0 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -721,8 +721,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); if(nC.nodeComesFromIncoming(node)) { ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() - && mMode == SHARED_ITEMS_MODE + if (mMode == SHARED_ITEMS_MODE && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedIncomingShares()); hideNodeActions(); @@ -731,8 +730,7 @@ && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { }); } else { ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() - && mMode == SHARED_ITEMS_MODE + if (mMode == SHARED_ITEMS_MODE && isNodeUnverified(state.getUnVerifiedOutgoingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedOutgoingShares()); hideNodeActions(); @@ -1103,6 +1101,7 @@ public void onClick(View v) { break; case R.id.verify_user_option: Intent authenticityCredentialsIntent = new Intent(getActivity(), AuthenticityCredentialsActivity.class); + authenticityCredentialsIntent.putExtra(Constants.IS_NODE_INCOMING, nC.nodeComesFromIncoming(node)); authenticityCredentialsIntent.putExtra(Constants.EMAIL, user.getEmail()); requireActivity().startActivity(authenticityCredentialsIntent); break; diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsActivity.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsActivity.kt index 0016711ed03..7bf62a65878 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsActivity.kt @@ -41,6 +41,10 @@ class AuthenticityCredentialsActivity : ComponentActivity() { viewModel.requestData(it) } ?: finish() + viewModel.setShowContactVerificationBanner( + intent.extras?.getBoolean(Constants.IS_NODE_INCOMING) ?: false + ) + setContent { AuthenticityCredentialsView() } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt index 10afc13c18f..150cfc91e1d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt @@ -143,4 +143,15 @@ class AuthenticityCredentialsViewModel @Inject constructor( * Updates state after shown error. */ fun errorShown() = _state.update { it.copy(error = null) } + + /** + * Function to update state with the boolean value to show contact verification banner + */ + fun setShowContactVerificationBanner(isNodeIncoming: Boolean = false) { + viewModelScope.launch { + _state.update { + it.copy(showContactVerificationBanner = isNodeIncoming) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt index 86fcca4430a..e5dc080ce56 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt @@ -10,6 +10,7 @@ import mega.privacy.android.domain.entity.contacts.AccountCredentials * @property isVerifyingCredentials True if is already verifying credentials, false otherwise. * @property myAccountCredentials [AccountCredentials.MyAccountCredentials]. * @property error String resource id for showing an error. + * @property showContactVerificationBanner Boolean to check if the node is incoming */ data class AuthenticityCredentialsState( val contactCredentials: AccountCredentials.ContactCredentials? = null, @@ -17,4 +18,5 @@ data class AuthenticityCredentialsState( val isVerifyingCredentials: Boolean = false, val myAccountCredentials: AccountCredentials.MyAccountCredentials? = null, val error: Int? = null, + val showContactVerificationBanner: Boolean = false, ) \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt index bbbe2ee4ad1..3af4183c2ca 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt @@ -134,7 +134,7 @@ fun ContactCredentials( .background(color = if (MaterialTheme.colors.isLight) white else dark_grey)) { Column { - if (isBannerVisible) { + if (state.showContactVerificationBanner && isBannerVisible) { Box(modifier = Modifier .testTag("CONTACT_VERIFICATION_BANNER_VIEW") .fillMaxWidth() diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt index 3591e9ff805..a54b6f569df 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt @@ -28,12 +28,12 @@ class SecurityUpgradeDialogFragment : DialogFragment() { */ @Inject lateinit var getThemeMode: GetThemeMode - + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = MaterialAlertDialogBuilder(requireContext()).setView( ComposeView(requireContext()).apply { setContent { - val nodeName = arguments?.getStringArrayList("nodeName") as List + val nodeName = arguments?.getStringArrayList("nodeNames") as List val mode by getThemeMode() .collectAsStateWithLifecycle(initialValue = ThemeMode.System) AndroidTheme(isDark = mode.isDarkMode()) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt index 2ba5c4a08dd..a3f22827574 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt @@ -42,7 +42,7 @@ import mega.privacy.android.core.ui.theme.subtitle1 @OptIn(ExperimentalComposeUiApi::class) @Composable fun SecurityUpgradeDialogView( - folderNames: List, + folderNames: List = emptyList(), onOkClick: () -> Unit, onCancelClick: () -> Unit, ) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt index 164f4422097..a1049eb2276 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt @@ -32,6 +32,7 @@ import mega.privacy.android.app.fragments.homepage.main.HomepageFragmentDirectio import mega.privacy.android.app.imageviewer.ImageViewerActivity.Companion.getIntentForSingleNode import mega.privacy.android.app.main.ManagerActivity import mega.privacy.android.app.main.PdfViewerActivity +import mega.privacy.android.app.main.controllers.NodeController import mega.privacy.android.app.modalbottomsheet.NodeOptionsBottomSheetDialogFragment import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity import mega.privacy.android.app.presentation.recentactions.model.RecentActionItemType @@ -75,6 +76,7 @@ class RecentActionsFragment : Fragment() { private lateinit var activityHiddenSpanned: Spanned private lateinit var listView: RecyclerView private lateinit var fastScroller: FastScroller + private lateinit var nodeController: NodeController private val viewModel: RecentActionsViewModel by activityViewModels() @@ -89,7 +91,7 @@ class RecentActionsFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - + nodeController = NodeController(requireActivity()) setupView() observeDragSupportEvents(viewLifecycleOwner, listView, Constants.VIEWER_FROM_RECETS) @@ -129,11 +131,17 @@ class RecentActionsFragment : Fragment() { */ private fun initAdapter() { adapter.setOnItemClickListener { item, position -> - if (!item.isKeyVerified) { - Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { - putExtra(Constants.EMAIL, item.bucket.userEmail) - requireActivity().startActivity(this) + lifecycleScope.launch { + viewModel.getMegaNode(item.bucket.nodes[0].id.longValue)?.let { megaNode -> + Intent(requireActivity(), + AuthenticityCredentialsActivity::class.java).apply { + putExtra(Constants.IS_NODE_INCOMING, + nodeController.nodeComesFromIncoming(megaNode)) + putExtra(Constants.EMAIL, item.bucket.userEmail) + requireActivity().startActivity(this) + } + } } } else { // If only one element in the bucket diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 28a3af32822..a142f35eee8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.R import mega.privacy.android.app.components.NewGridRecyclerView import mega.privacy.android.app.main.adapters.MegaNodeAdapter +import mega.privacy.android.app.main.controllers.NodeController import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.manager.model.Tab @@ -50,6 +51,8 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { private fun state() = viewModel.state.value + private lateinit var nodeController: NodeController + /** * onCreateView */ @@ -78,6 +81,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { */ override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + nodeController = NodeController(requireActivity()) setupObservers() } @@ -98,6 +102,8 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { val actualPosition = position - 1 if (state().unVerifiedIncomingNodeHandles.contains(state().nodes[actualPosition].handle)) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { + putExtra(Constants.IS_NODE_INCOMING, + nodeController.nodeComesFromIncoming(state().nodes[actualPosition])) putExtra(Constants.EMAIL, ContactUtil.getContactEmailDB(state().nodes[actualPosition].owner)) requireActivity().startActivity(this) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index 56ee6aaf12c..4169301854d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -14,7 +14,6 @@ import nz.mega.sdk.MegaNode * @param isInvalidHandle true if parent handle is invalid * @param isLoading true if the nodes are loading * @param sortOrder current sort order - * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedIncomingShares List of unverified incoming [ShareData] * @param unVerifiedIncomingNodeHandles List of unverified incoming node handles */ @@ -26,7 +25,6 @@ data class IncomingSharesState( val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedIncomingShares: List = emptyList(), val unVerifiedIncomingNodeHandles: List = emptyList(), ) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index b326a7e9987..64b886a5841 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -14,7 +14,6 @@ import nz.mega.sdk.MegaNode * @param isInvalidHandle true if handle is invalid * @param isLoading true if the nodes are loading * @param sortOrder current sort order - * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles */ @@ -26,7 +25,6 @@ data class OutgoingSharesState( val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodeHandles: List = emptyList(), ) { diff --git a/app/src/main/java/mega/privacy/android/app/utils/Constants.java b/app/src/main/java/mega/privacy/android/app/utils/Constants.java index 663849b3b3f..9fc44978796 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/Constants.java +++ b/app/src/main/java/mega/privacy/android/app/utils/Constants.java @@ -584,6 +584,7 @@ public class Constants { public static final int SCROLLING_UP_DIRECTION = -1; public static final int REQUIRE_PASSCODE_INVALID = -1; + public static final String IS_NODE_INCOMING = "isNodeIncoming"; public static final String CONTACT_HANDLE = "contactHandle"; public static final String SHOW_SNACKBAR = "SHOW_SNACKBAR"; public static final String CHAT_ID = "CHAT_ID"; diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt index 053860cf6c5..4297d92fe53 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt @@ -149,7 +149,8 @@ class AuthenticityCredentialsViewTest { initComposeRuleContent(AuthenticityCredentialsState( contactCredentials = contactCredentials, myAccountCredentials = AccountCredentials.MyAccountCredentials(myCredentials), - )) + showContactVerificationBanner = true, + )) composeTestRule.onNodeWithTag("CONTACT_VERIFICATION_BANNER_VIEW").assertExists() composeTestRule.onNodeWithText(R.string.shared_items_verify_credentials_verify_person_banner_label) .assertExists() From aefb8d7f4ded7c3bf7352a8f798696784d3d2a15 Mon Sep 17 00:00:00 2001 From: Bhavna Thacker Date: Wed, 22 Feb 2023 17:40:24 +0530 Subject: [PATCH 120/334] AND-15614: App crashes when accessing the notifications section --- .../java/mega/privacy/android/data/mapper/UserAlertMapper.kt | 3 +-- .../android/domain/usecase/DefaultMonitorUserAlerts.kt | 4 ++-- .../mega/privacy/android/domain/usecase/MonitorUserAlerts.kt | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/data/src/main/java/mega/privacy/android/data/mapper/UserAlertMapper.kt b/data/src/main/java/mega/privacy/android/data/mapper/UserAlertMapper.kt index d4ebb787614..fe380f452c0 100644 --- a/data/src/main/java/mega/privacy/android/data/mapper/UserAlertMapper.kt +++ b/data/src/main/java/mega/privacy/android/data/mapper/UserAlertMapper.kt @@ -370,8 +370,7 @@ private suspend fun MegaUserAlert.getUpdatedMeetingAlert( val createdTime = getTimestamp(CREATED_TIME_INDEX) val isRecurring = meeting?.rules != null val isOccurrence = pcrHandle.isValid() -// if (isOccurrence) { - if (false) { // TODO Temporarily disabled until a fix is found + if (isOccurrence) { scheduledMeetingOccurrProvider(nodeHandle)?.firstOrNull { occurr -> occurr.schedId == this.schedId && occurr.parentSchedId == this.pcrHandle diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultMonitorUserAlerts.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultMonitorUserAlerts.kt index 4aecfd40f9a..dfc34380970 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultMonitorUserAlerts.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultMonitorUserAlerts.kt @@ -17,9 +17,9 @@ import javax.inject.Inject class DefaultMonitorUserAlerts @Inject constructor( private val notificationsRepository: NotificationsRepository, ) : MonitorUserAlerts { - override fun invoke() = + override suspend fun invoke() = notificationsRepository.monitorUserAlerts().runningFold( - initial = runBlocking { notificationsRepository.getUserAlerts() }, + initial = notificationsRepository.getUserAlerts(), operation = { current, updates -> (updates.filterNot { it.isOwnChange } + current).distinctBy { it.id } } diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/MonitorUserAlerts.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/MonitorUserAlerts.kt index 0136ee92198..973b08aa7fc 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/MonitorUserAlerts.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/MonitorUserAlerts.kt @@ -13,5 +13,5 @@ fun interface MonitorUserAlerts { * * @return user alerts as a flow */ - operator fun invoke(): Flow> + suspend operator fun invoke(): Flow> } \ No newline at end of file From 2292fa5f6da2099f0e64eacde4606e9fd67aeb28 Mon Sep 17 00:00:00 2001 From: Bhavna Thacker Date: Wed, 22 Feb 2023 19:42:59 +0530 Subject: [PATCH 121/334] AND-15614: Apply suggestion notifications fix --- .../data/repository/DefaultNotificationsRepository.kt | 9 ++++++--- .../android/domain/usecase/DefaultMonitorUserAlerts.kt | 8 ++++++-- .../privacy/android/domain/usecase/MonitorUserAlerts.kt | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/mega/privacy/android/data/repository/DefaultNotificationsRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/DefaultNotificationsRepository.kt index 0e3dff6b634..2a2c44457b2 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/DefaultNotificationsRepository.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/DefaultNotificationsRepository.kt @@ -3,6 +3,7 @@ package mega.privacy.android.data.repository import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.withContext import mega.privacy.android.data.gateway.MegaLocalStorageGateway @@ -43,14 +44,16 @@ internal class DefaultNotificationsRepository @Inject constructor( .mapNotNull { (userAlerts) -> userAlerts?.map { withContext(dispatcher) { - userAlertsMapper(it, + userAlertsMapper( + it, ::provideContact, ::provideScheduledMeeting, ::provideSchedMeetingOccurrences, - megaApiGateway::getMegaNodeByHandle) + megaApiGateway::getMegaNodeByHandle + ) } } - } + }.flowOn(dispatcher) override fun monitorEvent(): Flow = megaApiGateway.globalUpdates .filterIsInstance() diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultMonitorUserAlerts.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultMonitorUserAlerts.kt index dfc34380970..0c0beaf5bd9 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultMonitorUserAlerts.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultMonitorUserAlerts.kt @@ -2,6 +2,8 @@ package mega.privacy.android.domain.usecase import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.emitAll +import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.runningFold import kotlinx.coroutines.runBlocking @@ -17,12 +19,14 @@ import javax.inject.Inject class DefaultMonitorUserAlerts @Inject constructor( private val notificationsRepository: NotificationsRepository, ) : MonitorUserAlerts { - override suspend fun invoke() = - notificationsRepository.monitorUserAlerts().runningFold( + override fun invoke() = flow { + val flow = notificationsRepository.monitorUserAlerts().runningFold( initial = notificationsRepository.getUserAlerts(), operation = { current, updates -> (updates.filterNot { it.isOwnChange } + current).distinctBy { it.id } } ).distinctUntilChanged() .mapLatest { list -> list.sortedByDescending { it.createdTime } } + emitAll(flow) + } } \ No newline at end of file diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/MonitorUserAlerts.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/MonitorUserAlerts.kt index 973b08aa7fc..0136ee92198 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/MonitorUserAlerts.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/MonitorUserAlerts.kt @@ -13,5 +13,5 @@ fun interface MonitorUserAlerts { * * @return user alerts as a flow */ - suspend operator fun invoke(): Flow> + operator fun invoke(): Flow> } \ No newline at end of file From a068ec7e27ec01189fa9e144233d6e1c386e767d Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 23 Feb 2023 05:54:15 +1300 Subject: [PATCH 122/334] AND-15739 - Race condition potential bug fixed in ManagerActivity --- .../assets/featuretoggle/feature_flags.json | 4 +- .../assets/featuretoggle/feature_flags.json | 4 ++ .../android/app/main/ManagerActivity.java | 2 +- .../SecurityUpgradeDialogView.kt | 63 ++++++++++++------- .../assets/featuretoggle/feature_flags.json | 4 -- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/app/src/debug/assets/featuretoggle/feature_flags.json b/app/src/debug/assets/featuretoggle/feature_flags.json index 676a4c1439e..25abad8e4c3 100644 --- a/app/src/debug/assets/featuretoggle/feature_flags.json +++ b/app/src/debug/assets/featuretoggle/feature_flags.json @@ -4,7 +4,7 @@ "value": true }, { - "name": "MandatoryFingerprintVerification", - "value": true + "name": "SetSecureFlag", + "value": false } ] \ No newline at end of file diff --git a/app/src/main/assets/featuretoggle/feature_flags.json b/app/src/main/assets/featuretoggle/feature_flags.json index 55e3a8e16d2..5f8aa914f37 100644 --- a/app/src/main/assets/featuretoggle/feature_flags.json +++ b/app/src/main/assets/featuretoggle/feature_flags.json @@ -2,5 +2,9 @@ { "name": "AppTest", "value": true + }, + { + "name": "SetSecureFlag", + "value": false } ] \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 210a8230d02..225abeacdac 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -2462,7 +2462,7 @@ private void collectFlows() { }); ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { - if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { + if (managerState.getShouldAlertUserAboutSecurityUpgrade()) { ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { outgoingFolderNames.clear(); for (int i = 0; i < outgoingSharesState.getNodes().size(); i++) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt index a3f22827574..f335bd5cfec 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt @@ -86,45 +86,60 @@ fun SecurityUpgradeDialogView( Spacer(Modifier.height(20.dp)) - Text(modifier = Modifier.testTag("SharedNodeInfo"), - text = pluralStringResource(id = R.plurals.shared_items_security_upgrade_dialog_node_sharing_info, - folderNames.size, TextUtils.join(", ", folderNames)), - style = body2.copy(textAlign = TextAlign.Center), - color = if (MaterialTheme.colors.isLight) { - Color.Black - } else { - Color.White - }) + if (folderNames.isNotEmpty()) { + Text( + modifier = Modifier.testTag("SharedNodeInfo"), + text = pluralStringResource( + id = R.plurals.shared_items_security_upgrade_dialog_node_sharing_info, + folderNames.size, TextUtils.join(", ", folderNames) + ), + style = body2.copy(textAlign = TextAlign.Center), + color = if (MaterialTheme.colors.isLight) { + Color.Black + } else { + Color.White + } + ) - Spacer(Modifier.height(20.dp)) + Spacer(Modifier.height(20.dp)) + + } - Button(modifier = Modifier - .height(45.dp) - .fillMaxWidth() - .padding(start = 25.dp, end = 25.dp), + Button( + modifier = Modifier + .height(45.dp) + .fillMaxWidth() + .padding(start = 25.dp, end = 25.dp), shape = RoundedCornerShape(8.dp), content = { - Text(text = stringResource(id = R.string.general_ok), - color = Color.White) + Text( + text = stringResource(id = R.string.general_ok), + color = Color.White + ) }, colors = ButtonDefaults.buttonColors(backgroundColor = jade_300), - onClick = onOkClick) + onClick = onOkClick + ) Spacer(Modifier.height(10.dp)) - Button(modifier = Modifier - .height(45.dp) - .fillMaxWidth() - .padding(start = 25.dp, end = 25.dp), + Button( + modifier = Modifier + .height(45.dp) + .fillMaxWidth() + .padding(start = 25.dp, end = 25.dp), shape = RoundedCornerShape(8.dp), colors = ButtonDefaults.buttonColors(backgroundColor = if (MaterialTheme.colors.isLight) Color.White else Color.DarkGray), - onClick = onCancelClick) { - Text(text = stringResource(id = R.string.button_cancel), + onClick = onCancelClick + ) { + Text( + text = stringResource(id = R.string.button_cancel), color = if (MaterialTheme.colors.isLight) { Color.Black } else { jade_300 - }) + } + ) } }) }) diff --git a/app/src/qa/assets/featuretoggle/feature_flags.json b/app/src/qa/assets/featuretoggle/feature_flags.json index 903fef89531..25abad8e4c3 100644 --- a/app/src/qa/assets/featuretoggle/feature_flags.json +++ b/app/src/qa/assets/featuretoggle/feature_flags.json @@ -3,10 +3,6 @@ "name": "PermanentLogging", "value": true }, - { - "name": "MandatoryFingerprintVerification", - "value": true - }, { "name": "SetSecureFlag", "value": false From 1325c240841b0e315c153bbc4a6e3b659712e145 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 23 Feb 2023 13:01:48 +0530 Subject: [PATCH 123/334] AND-15760 Fix share folder failure --- .../NodeOptionsBottomSheetViewModel.kt | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt index 3b483b693d8..ac1522122a9 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt @@ -32,18 +32,6 @@ class NodeOptionsBottomSheetViewModel @Inject constructor( */ val state: StateFlow = _state - /** - * Check if the handle is valid or not - * - * @param handle - * @return true if the handle is invalid - */ - private suspend fun isInvalidHandle(handle: Long = _state.value.currentNodeHandle): Boolean { - return handle - .takeUnless { it == -1L || it == MegaApiJava.INVALID_HANDLE } - ?.let { getNodeByHandle(it) == null } - ?: true - } /** * Calls OpenShareDialog use case to create crypto key for sharing @@ -53,7 +41,7 @@ class NodeOptionsBottomSheetViewModel @Inject constructor( fun callOpenShareDialog(nodeHandle: Long) { kotlin.runCatching { viewModelScope.launch { - if (!isInvalidHandle(nodeHandle)) { + if (nodeHandle != MegaApiJava.INVALID_HANDLE) { getNodeByHandle(nodeHandle)?.let { megaNode -> openShareDialog(megaNode) } From 5191009a6e9b61ac96d4025b1545d38b8cc15c0b Mon Sep 17 00:00:00 2001 From: Nikhil Date: Mon, 27 Feb 2023 15:12:49 +0530 Subject: [PATCH 124/334] SHR-48 Fix importing message not closing on import complete --- .../folderlink/FolderLinkActivity.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/folderlink/FolderLinkActivity.kt b/app/src/main/java/mega/privacy/android/app/presentation/folderlink/FolderLinkActivity.kt index 32d0ebb91a8..c318d050a1d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/folderlink/FolderLinkActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/folderlink/FolderLinkActivity.kt @@ -36,6 +36,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.jeremyliao.liveeventbus.LiveEventBus import dagger.hilt.android.AndroidEntryPoint import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.kotlin.subscribeBy import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.launch import mega.privacy.android.app.MegaApplication.Companion.getInstance @@ -367,13 +368,13 @@ class FolderLinkActivity : TransfersManagementActivity(), MegaRequestListenerInt checkNameCollisionUseCase.check(selectedNode, toHandle, NameCollisionType.COPY) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe { collision: NameCollision, throwable: Throwable? -> - if (throwable == null) { + .subscribeBy( + onSuccess = { collisionResult -> dismissAlertDialogIfExists(statusDialog) - val list: ArrayList = ArrayList() - list.add(collision) - nameCollisionActivityContract?.launch(list) - } else { + nameCollisionActivityContract?.launch(arrayListOf(collisionResult)) + }, + onError = { error -> + Timber.e(error, "No collision.") copyNodeUseCase.copy(selectedNode, toHandle) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) @@ -383,7 +384,7 @@ class FolderLinkActivity : TransfersManagementActivity(), MegaRequestListenerInt showCopyResult(null, copyThrowable) }) } - } + ) } else { Timber.w("Selected Node is NULL") @@ -836,6 +837,7 @@ class FolderLinkActivity : TransfersManagementActivity(), MegaRequestListenerInt * @param throwable */ private fun showCopyResult(copyRequestResult: CopyRequestResult?, throwable: Throwable?) { + dismissAlertDialogIfExists(statusDialog) clearSelections() hideMultipleSelect() if (copyRequestResult != null) { From fcf4ad07011c17736a9bf853c9f37758674387e1 Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Tue, 28 Feb 2023 10:34:46 +0800 Subject: [PATCH 125/334] CC-3763 The Media Discovery Dialog is flickered on time T4574771 AND - The media discovery view message is displayed incorrectly. --- .../mediadiscovery/MediaDiscoveryViewModel.kt | 9 ++++----- .../model/MediaDiscoveryViewState.kt | 17 ++++++++++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/photos/mediadiscovery/MediaDiscoveryViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/photos/mediadiscovery/MediaDiscoveryViewModel.kt index 55715ce4b5f..3cd7efbeebb 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/photos/mediadiscovery/MediaDiscoveryViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/photos/mediadiscovery/MediaDiscoveryViewModel.kt @@ -54,11 +54,10 @@ class MediaDiscoveryViewModel @Inject constructor( val sortOrder = getCameraSortOrder() setCurrentSort(sort = mapSortOrderToSort(sortOrder)) - monitorMediaDiscoveryView().collectLatest { mediaDiscoverViewSettings -> - mediaDiscoverViewSettings?.let { settings -> - _state.update { - it.copy(mediaDiscoveryViewSettings = settings) - } + monitorMediaDiscoveryView().collectLatest { mediaDiscoveryViewSettings -> + _state.update { + it.copy(mediaDiscoveryViewSettings = mediaDiscoveryViewSettings + ?: MediaDiscoveryViewSettings.INITIAL.ordinal) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/photos/mediadiscovery/model/MediaDiscoveryViewState.kt b/app/src/main/java/mega/privacy/android/app/presentation/photos/mediadiscovery/model/MediaDiscoveryViewState.kt index d3bca2c45ce..63d3775e0d1 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/photos/mediadiscovery/model/MediaDiscoveryViewState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/photos/mediadiscovery/model/MediaDiscoveryViewState.kt @@ -8,6 +8,21 @@ import mega.privacy.android.app.presentation.photos.model.ZoomLevel import mega.privacy.android.app.presentation.settings.model.MediaDiscoveryViewSettings import mega.privacy.android.domain.usecase.MonitorMediaDiscoveryView +/** + * Media Discovery View state + * + * @property uiPhotoList photo list + * @property currentZoomLevel current zoom level + * @property selectedPhotoIds selected photo ids + * @property currentSort current sort + * @property selectedTimeBarTab selected time bar tab + * @property yearsCardList years card list + * @property monthsCardList months card list + * @property daysCardList days card list + * @property scrollStartIndex the start index of scroll + * @property scrollStartOffset the start offset of scroll + * @property mediaDiscoveryViewSettings media discovery dialog view settings + */ data class MediaDiscoveryViewState( val uiPhotoList: List = emptyList(), val currentZoomLevel: ZoomLevel = ZoomLevel.Grid_3, @@ -19,5 +34,5 @@ data class MediaDiscoveryViewState( val daysCardList: List = emptyList(), val scrollStartIndex: Int = 0, val scrollStartOffset: Int = 0, - val mediaDiscoveryViewSettings: Int = MediaDiscoveryViewSettings.INITIAL.ordinal + val mediaDiscoveryViewSettings: Int? = null ) From 10a108d0c3dd1c336a6e2896c5ac3a92ebaebdcf Mon Sep 17 00:00:00 2001 From: Gonzalo Toledano Date: Tue, 28 Feb 2023 10:02:24 +1300 Subject: [PATCH 126/334] MEET-2020 Scheduled meetings mobile release support --- .../extensions/ChatScheduledMeeting.kt | 51 ++-- .../meeting/view/ScheduledMeetingInfoView.kt | 5 +- .../view/getRecurringMeetingDateTime.kt | 284 +++++++++++------- .../notification/model/Notification.kt | 8 +- .../model/SchedMeetingNotification.kt | 17 ++ .../model/extensions/AlertScheduledMeeting.kt | 38 ++- .../model/extensions/ChatDateText.kt | 70 ----- .../model/mapper/NotificationMapper.kt | 6 +- .../notification/view/NotificationItemView.kt | 46 +-- .../view/NotificationSchedMeetingView.kt | 55 ++++ .../notification/NotificationViewModelTest.kt | 13 +- .../notification/view/NotificationViewTest.kt | 62 ++-- build.gradle | 2 +- .../android/data/mapper/UserAlertMapper.kt | 18 +- .../data/repository/DefaultChatRepository.kt | 30 +- .../android/domain/entity/UserAlert.kt | 6 +- .../entity/chat/ChatScheduledMeeting.kt | 2 +- .../domain/repository/ChatRepository.kt | 10 +- .../domain/usecase/DefaultGetMeetings.kt | 59 ++-- 19 files changed, 419 insertions(+), 363 deletions(-) create mode 100644 app/src/main/java/mega/privacy/android/app/presentation/notification/model/SchedMeetingNotification.kt delete mode 100644 app/src/main/java/mega/privacy/android/app/presentation/notification/model/extensions/ChatDateText.kt create mode 100644 app/src/main/java/mega/privacy/android/app/presentation/notification/view/NotificationSchedMeetingView.kt diff --git a/app/src/main/java/mega/privacy/android/app/presentation/extensions/ChatScheduledMeeting.kt b/app/src/main/java/mega/privacy/android/app/presentation/extensions/ChatScheduledMeeting.kt index 00f382b73d5..ccfea15e886 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/extensions/ChatScheduledMeeting.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/extensions/ChatScheduledMeeting.kt @@ -40,13 +40,11 @@ fun ChatScheduledMeeting.isTomorrow(): Boolean { * @param is24HourFormat True, if it's 24 hour format. * @return Text of start time. */ -fun ChatScheduledMeeting.getStartTime(is24HourFormat: Boolean): String { - this.startDateTime?.let { - return getHourFormatter(Instant.ofEpochSecond(it), is24HourFormat).format(it.parseDate()) - } - - return "" -} +fun ChatScheduledMeeting.getStartTime(is24HourFormat: Boolean): String = + this.startDateTime?.let { startDate -> + val hourFormatter = getHourFormatter(is24HourFormat) + return hourFormatter.format(startDate.parseDate()) + } ?: "" /** * Get end time formatted @@ -54,43 +52,33 @@ fun ChatScheduledMeeting.getStartTime(is24HourFormat: Boolean): String { * @param is24HourFormat True, if it's 24 hour format. * @return Text of end time. */ -fun ChatScheduledMeeting.getEndTime(is24HourFormat: Boolean): String { - this.endDateTime?.let { - return getHourFormatter(Instant.ofEpochSecond(it), is24HourFormat).format(it.parseDate()) - } - - return "" -} +fun ChatScheduledMeeting.getEndTime(is24HourFormat: Boolean): String = + this.endDateTime?.let { endDate -> + val hourFormatter = getHourFormatter(is24HourFormat) + return hourFormatter.format(endDate.parseDate()) + } ?: "" /** * Get start date with weekday * * @return Text of start date */ -fun ChatScheduledMeeting.getCompleteStartDate(): String { - val dateFormatter = getCompleteDateFormatter() - +fun ChatScheduledMeeting.getCompleteStartDate(): String = this.startDateTime?.let { start -> + val dateFormatter = getCompleteDateFormatter() return dateFormatter.format(start.parseDate()) - } - - return "" -} + } ?: "" /** * Get start date without weekday * * @return Text of start date */ -fun ChatScheduledMeeting.getStartDate(): String { - val dateFormatter = getDateFormatter() - +fun ChatScheduledMeeting.getStartDate(): String = this.startDateTime?.let { start -> + val dateFormatter = getDateFormatter() return dateFormatter.format(start.parseDate()) - } - - return "" -} + } ?: "" /** * Get end date without weekday @@ -174,23 +162,22 @@ private fun Long.parseDate(): ZonedDateTime = ZonedDateTime.ofInstant(Instant.ofEpochSecond(this), ZoneOffset.UTC) private fun getUntilDate(until: Long): String? { - val dateFormatter = getDateFormatter() if (until != 0L) { + val dateFormatter = getDateFormatter() return dateFormatter.format(until.parseDate()) } return null } -private fun getHourFormatter(instant: Instant, is24HourFormat: Boolean): String = +private fun getHourFormatter(is24HourFormat: Boolean): DateTimeFormatter = DateTimeFormatter .ofPattern(if (is24HourFormat) "HH:mm" else "hh:mma") .withZone(ZoneId.systemDefault()) - .format(instant) private fun getCompleteDateFormatter(): DateTimeFormatter = DateTimeFormatter - .ofPattern("EEEE',' d MMM yyyy") + .ofPattern("E',' d MMM',' yyyy") .withZone(ZoneId.systemDefault()) private fun getDateFormatter(): DateTimeFormatter = diff --git a/app/src/main/java/mega/privacy/android/app/presentation/meeting/view/ScheduledMeetingInfoView.kt b/app/src/main/java/mega/privacy/android/app/presentation/meeting/view/ScheduledMeetingInfoView.kt index 3f76c31fc06..c141e0cc55b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/meeting/view/ScheduledMeetingInfoView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/meeting/view/ScheduledMeetingInfoView.kt @@ -430,10 +430,7 @@ private fun ScheduledMeetingSubtitle(state: ScheduledMeetingInfoState) { maxLines = 2, overflow = TextOverflow.Ellipsis) } else { - val text = - getRecurringMeetingDateTime(schedMeet, state.is24HourFormat).replace("[A]", "") - .replace("[/A]", "") - .replace("[B]", "").replace("[/B]", "") + val text = getRecurringMeetingDateTime(schedMeet, state.is24HourFormat) if (text.isNotEmpty()) { Text(text = text, style = MaterialTheme.typography.subtitle2, diff --git a/app/src/main/java/mega/privacy/android/app/presentation/meeting/view/getRecurringMeetingDateTime.kt b/app/src/main/java/mega/privacy/android/app/presentation/meeting/view/getRecurringMeetingDateTime.kt index 3971f33ef53..de0b9ada97b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/meeting/view/getRecurringMeetingDateTime.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/meeting/view/getRecurringMeetingDateTime.kt @@ -4,6 +4,11 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle import mega.privacy.android.app.R import mega.privacy.android.app.presentation.extensions.getCompleteStartDate import mega.privacy.android.app.presentation.extensions.getEndDate @@ -19,33 +24,44 @@ import mega.privacy.android.domain.entity.meeting.OccurrenceFrequencyType import mega.privacy.android.domain.entity.meeting.WeekOfMonth import mega.privacy.android.domain.entity.meeting.Weekday +private const val PLACEHOLDER_A_OPEN = "[A]" +private const val PLACEHOLDER_A_CLOSE = "[/A]" +private const val PLACEHOLDER_B_OPEN = "[B]" +private const val PLACEHOLDER_B_CLOSE = "[/B]" + /** * Get the appropriate day and time string for a scheduled meeting. * * @param scheduledMeeting [ChatScheduledMeeting] * @param is24HourFormat True, if it's 24 hour format. False, if not. + * @param highLightTime Flag to highlight time differences + * @param highLightDate Flag to highlight date differences */ @OptIn(ExperimentalComposeUiApi::class) @Composable fun getRecurringMeetingDateTime( scheduledMeeting: ChatScheduledMeeting, is24HourFormat: Boolean, -): String { + highLightTime: Boolean = false, + highLightDate: Boolean = false, +): AnnotatedString { + var result = "" val rules = scheduledMeeting.rules val startTime = scheduledMeeting.getStartTime(is24HourFormat) val endTime = scheduledMeeting.getEndTime(is24HourFormat) val startDate = scheduledMeeting.getStartDate() val endDate = scheduledMeeting.getEndDate() - if (rules == null) { - return getTextForOneOffMeeting(scheduledMeeting, startTime, endTime) - } - - when (rules.freq) { - OccurrenceFrequencyType.Invalid -> { - return getTextForOneOffMeeting(scheduledMeeting, startTime, endTime) + when (rules?.freq) { + null, OccurrenceFrequencyType.Invalid -> { + result = getTextForOneOffMeeting( + scheduledMeeting, + startTime, + endTime, + highLightTime || highLightDate + ) } - OccurrenceFrequencyType.Daily -> return when { + OccurrenceFrequencyType.Daily -> result = when { scheduledMeeting.isForever() -> stringResource( id = R.string.notification_subtitle_scheduled_meeting_recurring_daily_forever, startDate, @@ -61,81 +77,76 @@ fun getRecurringMeetingDateTime( ) } OccurrenceFrequencyType.Weekly -> { - rules.weekDayList?.takeIf { it.isNotEmpty() }?.let { weekDaysList -> - val interval = scheduledMeeting.getIntervalValue() - when (weekDaysList.size) { - 1 -> { - val weekDay = getWeekDay(weekDaysList.first(), true) - return when { - scheduledMeeting.isForever() -> pluralStringResource( - R.plurals.notification_subtitle_scheduled_meeting_recurring_weekly_one_day_forever, - interval, - weekDay, - interval, - startDate, - startTime, - endTime - ) - else -> pluralStringResource( - R.plurals.notification_subtitle_scheduled_meeting_recurring_weekly_one_day_until, - interval, - weekDay, - interval, - startDate, - endDate, - startTime, - endTime - ) - } - } - else -> { - var weekdayStringList: String - val firstPos = weekDaysList.sorted().indexOf(weekDaysList.minOf { it }) - val lastPos = weekDaysList.sorted().indexOf(weekDaysList.maxOf { it }) - mutableListOf().apply { - weekDaysList.sorted().forEach { day -> - val index = weekDaysList.indexOf(day) - if (index != lastPos) { - this@apply.add(getWeekDay(day, index == firstPos)) - } + rules.weekDayList?.takeIf { it.isNotEmpty() }?.sortedBy { it.ordinal } + ?.let { weekDaysList -> + val interval = scheduledMeeting.getIntervalValue() + when (weekDaysList.size) { + 1 -> { + val weekDay = getWeekDay(weekDaysList.first(), true) + result = when { + scheduledMeeting.isForever() -> pluralStringResource( + R.plurals.notification_subtitle_scheduled_meeting_recurring_weekly_one_day_forever, + interval, + weekDay, + interval, + startDate, + startTime, + endTime + ) + else -> pluralStringResource( + R.plurals.notification_subtitle_scheduled_meeting_recurring_weekly_one_day_until, + interval, + weekDay, + interval, + startDate, + endDate, + startTime, + endTime + ) } - val separator = ", " - weekdayStringList = this@apply.joinToString(separator) } - val lastWeekDay = getWeekDay(weekDaysList.last(), false) - return when { - scheduledMeeting.isForever() -> - pluralStringResource( + else -> { + val lastWeekDay = getWeekDay(weekDaysList.last(), false) + val weekDaysListString = StringBuilder().apply { + weekDaysList.forEachIndexed { index, weekday -> + if (index != weekDaysList.size - 1) { + append(getWeekDay(weekday, index == 0)) + if (index != weekDaysList.size - 2) append(", ") + } + } + }.toString() + result = when { + scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_weekly_several_days_forever, interval, - weekdayStringList, + weekDaysListString, + lastWeekDay, + interval, + startDate, + startTime, + endTime + ) + else -> pluralStringResource( + R.plurals.notification_subtitle_scheduled_meeting_recurring_weekly_several_days_until, + interval, + weekDaysListString, lastWeekDay, interval, startDate, + endDate, startTime, endTime ) - else -> pluralStringResource( - R.plurals.notification_subtitle_scheduled_meeting_recurring_weekly_several_days_until, - interval, - weekdayStringList, - lastWeekDay, - interval, - startDate, - endDate, - startTime, - endTime - ) + } } } } - } } OccurrenceFrequencyType.Monthly -> { val interval = scheduledMeeting.getIntervalValue() rules.monthDayList?.takeIf { it.isNotEmpty() }?.let { monthDayList -> val dayOfTheMonth = monthDayList.first() - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_single_day_forever, interval, @@ -166,7 +177,7 @@ fun getRecurringMeetingDateTime( Weekday.Monday -> { when (weekOfMonth) { WeekOfMonth.First -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_monday_first, interval, @@ -186,7 +197,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Second -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_monday_second, interval, @@ -206,7 +217,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Third -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_monday_third, interval, @@ -226,7 +237,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Fourth -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_monday_fourth, interval, @@ -246,7 +257,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Fifth -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_monday_fifth, interval, @@ -270,7 +281,7 @@ fun getRecurringMeetingDateTime( Weekday.Tuesday -> { when (weekOfMonth) { WeekOfMonth.First -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_tuesday_first, interval, @@ -290,7 +301,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Second -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_tuesday_second, interval, @@ -310,7 +321,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Third -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_tuesday_third, interval, @@ -330,7 +341,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Fourth -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_tuesday_fourth, interval, @@ -350,7 +361,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Fifth -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_tuesday_fifth, interval, @@ -374,7 +385,7 @@ fun getRecurringMeetingDateTime( Weekday.Wednesday -> { when (weekOfMonth) { WeekOfMonth.First -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_wednesday_first, interval, @@ -394,7 +405,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Second -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_wednesday_second, interval, @@ -414,7 +425,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Third -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_wednesday_third, interval, @@ -434,7 +445,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Fourth -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_wednesday_fourth, interval, @@ -454,7 +465,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Fifth -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_wednesday_fifth, interval, @@ -478,7 +489,7 @@ fun getRecurringMeetingDateTime( Weekday.Thursday -> { when (weekOfMonth) { WeekOfMonth.First -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_thursday_first, interval, @@ -498,7 +509,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Second -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_thursday_second, interval, @@ -518,7 +529,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Third -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_thursday_third, interval, @@ -538,7 +549,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Fourth -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_thursday_fourth, interval, @@ -558,7 +569,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Fifth -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_thursday_fifth, interval, @@ -582,7 +593,7 @@ fun getRecurringMeetingDateTime( Weekday.Friday -> { when (weekOfMonth) { WeekOfMonth.First -> { - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_friday_first, interval, @@ -603,7 +614,7 @@ fun getRecurringMeetingDateTime( } } WeekOfMonth.Second -> { - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_friday_second, interval, @@ -624,7 +635,7 @@ fun getRecurringMeetingDateTime( } } WeekOfMonth.Third -> { - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_friday_third, interval, @@ -645,7 +656,7 @@ fun getRecurringMeetingDateTime( } } WeekOfMonth.Fourth -> { - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_friday_fourth, interval, @@ -666,7 +677,7 @@ fun getRecurringMeetingDateTime( } } WeekOfMonth.Fifth -> { - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_friday_fifth, interval, @@ -691,7 +702,7 @@ fun getRecurringMeetingDateTime( Weekday.Saturday -> { when (weekOfMonth) { WeekOfMonth.First -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_saturday_first, interval, @@ -711,7 +722,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Second -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_saturday_second, interval, @@ -731,7 +742,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Third -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_saturday_third, interval, @@ -751,7 +762,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Fourth -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_saturday_fourth, interval, @@ -771,7 +782,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Fifth -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_saturday_fifth, interval, @@ -795,7 +806,7 @@ fun getRecurringMeetingDateTime( Weekday.Sunday -> { when (weekOfMonth) { WeekOfMonth.First -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_sunday_first, interval, @@ -815,7 +826,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Second -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_sunday_second, interval, @@ -835,7 +846,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Third -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_sunday_third, interval, @@ -855,7 +866,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Fourth -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_sunday_fourth, interval, @@ -875,7 +886,7 @@ fun getRecurringMeetingDateTime( ) } WeekOfMonth.Fifth -> - return when { + result = when { scheduledMeeting.isForever() -> pluralStringResource( R.plurals.notification_subtitle_scheduled_meeting_recurring_monthly_ordinal_day_forever_sunday_fifth, interval, @@ -900,16 +911,79 @@ fun getRecurringMeetingDateTime( } } } - - return "" + return formatAnnotatedString(result, highLightTime, highLightDate) } +private fun formatAnnotatedString( + dateTime: String, + highLightTime: Boolean, + highLightDate: Boolean +): AnnotatedString = + buildAnnotatedString { + if (!highLightDate && !highLightTime) { + append( + dateTime + .replace(PLACEHOLDER_A_OPEN, "").replace(PLACEHOLDER_A_CLOSE, "") + .replace(PLACEHOLDER_B_OPEN, "").replace(PLACEHOLDER_B_CLOSE, "") + ) + } else { + val indexAOpenStart = dateTime.indexOf(PLACEHOLDER_A_OPEN) + val indexAOpenEnd = indexAOpenStart + PLACEHOLDER_A_OPEN.length + val indexACloseStart = dateTime.indexOf(PLACEHOLDER_A_CLOSE) + val indexACloseEnd = indexACloseStart + PLACEHOLDER_A_CLOSE.length + + val indexBOpenStart = dateTime.indexOf(PLACEHOLDER_B_OPEN) + val indexBOpenEnd = indexBOpenStart + PLACEHOLDER_B_OPEN.length + val indexBCloseStart = dateTime.indexOf(PLACEHOLDER_B_CLOSE) + val indexBCloseEnd = indexBCloseStart + PLACEHOLDER_B_CLOSE.length + + if (indexAOpenStart != -1) { + append(dateTime.substring(0, indexAOpenStart)) + } else if (indexBOpenStart != -1) { + append(dateTime.substring(0, indexBOpenStart)) + } else { // Nothing to highlight + append(dateTime) + return@buildAnnotatedString + } + + if (indexAOpenStart != -1) { + if (highLightDate) { + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(dateTime.substring(indexAOpenEnd, indexACloseStart)) + } + } else { + append(dateTime.substring(indexAOpenEnd, indexACloseStart)) + } + } + + if (indexBOpenStart != -1) { + append(dateTime.substring(indexACloseEnd, indexBOpenStart)) + } else { + append(dateTime.substring(indexACloseEnd, dateTime.lastIndex)) + return@buildAnnotatedString + } + + if (highLightTime) { + withStyle(SpanStyle(fontWeight = FontWeight.Bold)) { + append(dateTime.substring(indexBOpenEnd, indexBCloseStart)) + } + } else { + append(dateTime.substring(indexBOpenEnd, indexBCloseStart)) + } + + if (indexBCloseEnd < dateTime.lastIndex) { + append(dateTime.substring(indexBCloseEnd, dateTime.lastIndex)) + } + } + } + /** * Get Text of subtitle for one off meeting * * @param scheduledMeeting [ChatScheduledMeeting] * @param startTime Start time * @param endTime End time + * @param highlightDateTime Flag to highlight date or time * @return Text of one off meeting */ @Composable @@ -917,9 +991,11 @@ private fun getTextForOneOffMeeting( scheduledMeeting: ChatScheduledMeeting, startTime: String, endTime: String, + highlightDateTime: Boolean, ): String = stringResource( id = when { + highlightDateTime -> R.string.notification_subtitle_scheduled_meeting_one_off scheduledMeeting.isToday() -> R.string.meetings_one_off_occurrence_info_today scheduledMeeting.isTomorrow() -> R.string.meetings_one_off_occurrence_info_tomorrow else -> R.string.notification_subtitle_scheduled_meeting_one_off @@ -945,4 +1021,4 @@ private fun getWeekDay(day: Weekday, isForSentenceStart: Boolean): String = when Weekday.Friday -> stringResource(id = if (isForSentenceStart) R.string.notification_scheduled_meeting_week_day_sentence_start_fri else R.string.notification_scheduled_meeting_week_day_sentence_middle_fri) Weekday.Saturday -> stringResource(id = if (isForSentenceStart) R.string.notification_scheduled_meeting_week_day_sentence_start_sat else R.string.notification_scheduled_meeting_week_day_sentence_middle_sat) Weekday.Sunday -> stringResource(id = if (isForSentenceStart) R.string.notification_scheduled_meeting_week_day_sentence_start_sun else R.string.notification_scheduled_meeting_week_day_sentence_middle_sun) -} \ No newline at end of file +} diff --git a/app/src/main/java/mega/privacy/android/app/presentation/notification/model/Notification.kt b/app/src/main/java/mega/privacy/android/app/presentation/notification/model/Notification.kt index 64ad81837fa..4f9be1ae68e 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/notification/model/Notification.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/notification/model/Notification.kt @@ -3,9 +3,7 @@ package mega.privacy.android.app.presentation.notification.model import android.content.Context import androidx.annotation.ColorRes import androidx.annotation.DrawableRes -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.unit.Dp -import mega.privacy.android.domain.entity.chat.ChatScheduledMeeting /** * Notification @@ -18,8 +16,7 @@ import mega.privacy.android.domain.entity.chat.ChatScheduledMeeting * @property titleMaxWidth * @property description * @property descriptionMaxWidth - * @property chatDateText - * @property recurringSchedMeeting + * @property schedMeetingNotification * @property dateText * @property isNew * @property backgroundColor @@ -36,8 +33,7 @@ data class Notification constructor( val titleMaxWidth: (Context) -> Int?, val description: (Context) -> CharSequence?, val descriptionMaxWidth: (Context) -> Int?, - val chatDateText: (Context) -> AnnotatedString?, - val recurringSchedMeeting: ChatScheduledMeeting?, + val schedMeetingNotification: SchedMeetingNotification?, val dateText: (Context) -> String, val isNew: Boolean, val backgroundColor: (Context) -> String, diff --git a/app/src/main/java/mega/privacy/android/app/presentation/notification/model/SchedMeetingNotification.kt b/app/src/main/java/mega/privacy/android/app/presentation/notification/model/SchedMeetingNotification.kt new file mode 100644 index 00000000000..f5f2471808f --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/presentation/notification/model/SchedMeetingNotification.kt @@ -0,0 +1,17 @@ +package mega.privacy.android.app.presentation.notification.model + +import mega.privacy.android.domain.entity.chat.ChatScheduledMeeting + +/** + * Sched meeting notification + * + * @property hasTimeChanged + * @property hasDateChanged + * @property scheduledMeeting + * @constructor Create empty Sched meeting notification + */ +data class SchedMeetingNotification constructor( + val scheduledMeeting: ChatScheduledMeeting?, + val hasTimeChanged: Boolean = false, + val hasDateChanged: Boolean = false, +) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/notification/model/extensions/AlertScheduledMeeting.kt b/app/src/main/java/mega/privacy/android/app/presentation/notification/model/extensions/AlertScheduledMeeting.kt index ff41492b4f2..566ee087407 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/notification/model/extensions/AlertScheduledMeeting.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/notification/model/extensions/AlertScheduledMeeting.kt @@ -1,13 +1,41 @@ package mega.privacy.android.app.presentation.notification.model.extensions +import mega.privacy.android.app.presentation.notification.model.SchedMeetingNotification import mega.privacy.android.domain.entity.ScheduledMeetingAlert +import mega.privacy.android.domain.entity.UpdatedScheduledMeetingDateTimeAlert import mega.privacy.android.domain.entity.UserAlert -import mega.privacy.android.domain.entity.chat.ChatScheduledMeeting /** - * Recurring Chat Scheduled meeting for the User Alert + * Scheduled meeting notification based on an User Alert * - * @return ChatScheduledMeeting + * @return SchedMeetingNotification */ -internal fun UserAlert.recurringScheduledMeeting(): ChatScheduledMeeting? = - if (this is ScheduledMeetingAlert && isRecurring) scheduledMeeting else null +internal fun UserAlert.schedMeetingNotification(): SchedMeetingNotification? = + if (this is ScheduledMeetingAlert) { + SchedMeetingNotification( + scheduledMeeting = scheduledMeeting?.copy( + startDateTime = startDate ?: scheduledMeeting?.startDateTime, + endDateTime = endDate ?: scheduledMeeting?.endDateTime, + ), + hasTimeChanged = hasTimeChanged(), + hasDateChanged = hasDateChanged(), + ) + } else { + null + } + +/** + * Check if Scheduled meeting time has changed + * + * @return true if has changed, false otherwise + */ +internal fun UserAlert.hasTimeChanged(): Boolean = + this is UpdatedScheduledMeetingDateTimeAlert && hasTimeChanged + +/** + * Check if Scheduled meeting date has changed + * + * @return true if has changed, false otherwise + */ +internal fun UserAlert.hasDateChanged(): Boolean = + this is UpdatedScheduledMeetingDateTimeAlert && hasDateChanged diff --git a/app/src/main/java/mega/privacy/android/app/presentation/notification/model/extensions/ChatDateText.kt b/app/src/main/java/mega/privacy/android/app/presentation/notification/model/extensions/ChatDateText.kt deleted file mode 100644 index c4bf8c5f075..00000000000 --- a/app/src/main/java/mega/privacy/android/app/presentation/notification/model/extensions/ChatDateText.kt +++ /dev/null @@ -1,70 +0,0 @@ -package mega.privacy.android.app.presentation.notification.model.extensions - -import android.content.Context -import android.text.format.DateFormat -import androidx.compose.ui.text.AnnotatedString -import androidx.compose.ui.text.SpanStyle -import androidx.compose.ui.text.font.FontWeight -import mega.privacy.android.domain.entity.ScheduledMeetingAlert -import mega.privacy.android.domain.entity.UpdatedScheduledMeetingDateTimeAlert -import mega.privacy.android.domain.entity.UserAlert -import java.time.Instant -import java.time.ZoneId -import java.time.ZoneOffset -import java.time.ZonedDateTime -import java.time.format.DateTimeFormatter - -/** - * Chat Date text - * - */ -internal fun UserAlert.chatDateText(): (Context) -> AnnotatedString? = { context -> - if (this is ScheduledMeetingAlert && startDate != null && endDate != null) { - val startDateTime = startDate?.toZonedDateTime() - val endDateTime = endDate?.toZonedDateTime() - val hourFormatter = getHourFormatter(context) - - AnnotatedString.Builder().apply { - val dateText = getDateFormatter().format(startDateTime) - if (this@chatDateText is UpdatedScheduledMeetingDateTimeAlert && hasDateChanged) { - append(dateText.boldStyle()) - } else { - append(dateText) - } - - append(" · ") - - val timeText = - "${hourFormatter.format(startDateTime)} - ${hourFormatter.format(endDateTime)}" - if (this@chatDateText is UpdatedScheduledMeetingDateTimeAlert && hasTimeChanged) { - append(timeText.boldStyle()) - } else { - append(timeText) - } - }.toAnnotatedString() - } else { - null - } -} - -private fun Long.toZonedDateTime(): ZonedDateTime = - ZonedDateTime.ofInstant(Instant.ofEpochSecond(this), ZoneOffset.UTC) - -private fun String.boldStyle(): AnnotatedString = - AnnotatedString(this, SpanStyle(fontWeight = FontWeight.Bold)) - -private fun getHourFormatter(context: Context): DateTimeFormatter = - DateTimeFormatter - .ofPattern( - if (DateFormat.is24HourFormat(context)) { - "HH:mm" - } else { - "hh:mm a" - } - ) - .withZone(ZoneId.systemDefault()) - -private fun getDateFormatter(): DateTimeFormatter = - DateTimeFormatter - .ofPattern("E',' d MMM',' yyyy") - .withZone(ZoneId.systemDefault()) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/notification/model/mapper/NotificationMapper.kt b/app/src/main/java/mega/privacy/android/app/presentation/notification/model/mapper/NotificationMapper.kt index fce8ef8aaeb..8f9c726aadf 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/notification/model/mapper/NotificationMapper.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/notification/model/mapper/NotificationMapper.kt @@ -2,12 +2,11 @@ package mega.privacy.android.app.presentation.notification.model.mapper import mega.privacy.android.app.presentation.notification.model.Notification import mega.privacy.android.app.presentation.notification.model.extensions.backgroundColor -import mega.privacy.android.app.presentation.notification.model.extensions.chatDateText import mega.privacy.android.app.presentation.notification.model.extensions.dateText import mega.privacy.android.app.presentation.notification.model.extensions.description import mega.privacy.android.app.presentation.notification.model.extensions.descriptionMaxWidth import mega.privacy.android.app.presentation.notification.model.extensions.onClick -import mega.privacy.android.app.presentation.notification.model.extensions.recurringScheduledMeeting +import mega.privacy.android.app.presentation.notification.model.extensions.schedMeetingNotification import mega.privacy.android.app.presentation.notification.model.extensions.sectionColour import mega.privacy.android.app.presentation.notification.model.extensions.sectionIcon import mega.privacy.android.app.presentation.notification.model.extensions.sectionTitle @@ -35,8 +34,7 @@ internal fun getNotification(alert: UserAlert) = Notification( titleMaxWidth = alert.titleMaxWidth(), description = alert.description(), descriptionMaxWidth = alert.descriptionMaxWidth(), - chatDateText = alert.chatDateText(), - recurringSchedMeeting = alert.recurringScheduledMeeting(), + schedMeetingNotification = alert.schedMeetingNotification(), dateText = alert.dateText(), isNew = !alert.seen, backgroundColor = alert.backgroundColor(), diff --git a/app/src/main/java/mega/privacy/android/app/presentation/notification/view/NotificationItemView.kt b/app/src/main/java/mega/privacy/android/app/presentation/notification/view/NotificationItemView.kt index 40778db3f92..7a0ee570c68 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/notification/view/NotificationItemView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/notification/view/NotificationItemView.kt @@ -2,7 +2,6 @@ package mega.privacy.android.app.presentation.notification.view import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.text.TextUtils -import android.text.format.DateFormat import android.util.TypedValue import android.widget.TextView import androidx.compose.foundation.background @@ -25,7 +24,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview @@ -34,7 +32,6 @@ import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.graphics.toColorInt import mega.privacy.android.app.R -import mega.privacy.android.app.presentation.meeting.view.getRecurringMeetingDateTime import mega.privacy.android.app.presentation.notification.model.Notification import mega.privacy.android.app.utils.StyleUtils.setTextStyle import mega.privacy.android.core.ui.controls.dpToSp @@ -76,22 +73,8 @@ internal fun NotificationItemView( NotificationDescription(notification) } - val schedMeetingText = notification.recurringSchedMeeting?.let { meeting -> - val is24HourFormat = DateFormat.is24HourFormat(LocalContext.current) - getRecurringMeetingDateTime(scheduledMeeting = meeting, is24HourFormat = is24HourFormat) - .replace("[A]", "") - .replace("[/A]", "") - .replace("[B]", "") - .replace("[/B]", "") - } - - if (!schedMeetingText.isNullOrBlank()) { - NotificationSchedMeetingTime(AnnotatedString(schedMeetingText)) - } else { - val chatDateText = notification.chatDateText(LocalContext.current) - if (!chatDateText.isNullOrBlank()) { - NotificationSchedMeetingTime(chatDateText) - } + if (notification.schedMeetingNotification != null) { + NotificationSchedMeetingView(notification.schedMeetingNotification) } NotificationDate(notification) @@ -226,21 +209,6 @@ private fun NotificationDescription( ) } -@Composable -private fun NotificationSchedMeetingTime(dateText: AnnotatedString) { - Text( - text = dateText, - modifier = Modifier - .padding(start = 16.dp, top = 2.dp, end = 16.dp) - .testTag("SchedMeetingTime"), - color = if (MaterialTheme.colors.isLight) grey_alpha_087 else white_alpha_087, - style = MaterialTheme.typography.caption, - fontSize = 12.sp, - maxLines = 2, - overflow = TextOverflow.Ellipsis - ) -} - @Composable private fun NotificationDate( notification: Notification, @@ -271,7 +239,8 @@ private fun NotificationDivider(horizontalPadding: Int) { @Preview(uiMode = UI_MODE_NIGHT_YES, name = "PreviewNotificationItemViewDark") @Composable private fun PreviewNotificationItemView() { - val notification = Notification(sectionTitle = { "CONTACTS" }, + val notification = Notification( + sectionTitle = { "CONTACTS" }, sectionColour = R.color.orange_400_orange_300, sectionIcon = null, title = { "New Contact" }, @@ -279,13 +248,12 @@ private fun PreviewNotificationItemView() { titleMaxWidth = { 200 }, description = { "xyz@gmail.com is now a contact" }, descriptionMaxWidth = { 300 }, - chatDateText = { AnnotatedString("Tue, 30 Jun, 2022 • 10:00 - 11:00") }, - recurringSchedMeeting = null, + schedMeetingNotification = null, dateText = { "11 October 2022 6:46 pm" }, isNew = true, backgroundColor = { "#D3D3D3" }, - separatorMargin = { 0 }, - onClick = {}) + separatorMargin = { 0 } + ) {} NotificationItemView(modifier = Modifier, notification, position = 0, notifications = listOf(notification), onClick = {}) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/notification/view/NotificationSchedMeetingView.kt b/app/src/main/java/mega/privacy/android/app/presentation/notification/view/NotificationSchedMeetingView.kt new file mode 100644 index 00000000000..0ce59a4bc7f --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/presentation/notification/view/NotificationSchedMeetingView.kt @@ -0,0 +1,55 @@ +package mega.privacy.android.app.presentation.notification.view + +import android.content.res.Configuration +import android.text.format.DateFormat +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import mega.privacy.android.app.presentation.meeting.view.getRecurringMeetingDateTime +import mega.privacy.android.app.presentation.notification.model.SchedMeetingNotification +import mega.privacy.android.core.ui.theme.grey_alpha_087 +import mega.privacy.android.core.ui.theme.white_alpha_087 + +/** + * Scheduled Meeting notification meeting view + * + * @param notification + */ +@Composable +fun NotificationSchedMeetingView(notification: SchedMeetingNotification) { + val dateText = getRecurringMeetingDateTime( + scheduledMeeting = notification.scheduledMeeting!!, + is24HourFormat = DateFormat.is24HourFormat(LocalContext.current), + highLightTime = notification.hasTimeChanged, + highLightDate = notification.hasDateChanged, + ) + + Text( + text = dateText, + modifier = Modifier + .padding(start = 16.dp, top = 2.dp, end = 16.dp) + .testTag("SchedMeetingTime"), + color = if (MaterialTheme.colors.isLight) grey_alpha_087 else white_alpha_087, + style = MaterialTheme.typography.caption, + fontSize = 12.sp, + maxLines = 2, + overflow = TextOverflow.Ellipsis + ) +} + +@Preview +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, name = "PreviewNotificationSchedMeetingTime") +@Composable +private fun PreviewNotificationSchedMeetingView() { + NotificationSchedMeetingView( + notification = SchedMeetingNotification(null) + ) +} diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/notification/NotificationViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/notification/NotificationViewModelTest.kt index b4580eb9123..bb1f416dfbf 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/notification/NotificationViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/notification/NotificationViewModelTest.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.presentation.notification.NotificationViewModel import mega.privacy.android.app.presentation.notification.model.Notification +import mega.privacy.android.app.presentation.notification.model.SchedMeetingNotification import mega.privacy.android.domain.entity.UserAlert import mega.privacy.android.domain.usecase.AcknowledgeUserAlerts import mega.privacy.android.domain.usecase.MonitorUserAlerts @@ -77,14 +78,12 @@ class NotificationViewModelTest { titleMaxWidth = { 200 }, description = { "" }, descriptionMaxWidth = { 300 }, + schedMeetingNotification = null, dateText = { "" }, isNew = true, backgroundColor = { "#D3D3D3" }, separatorMargin = { 0 }, - onClick = {}, - chatDateText = { null }, - recurringSchedMeeting = null, - ) + ) {} val alert = mock() whenever(monitorUserAlerts()).thenReturn(flowOf(listOf(alert))) @@ -111,14 +110,12 @@ class NotificationViewModelTest { titleMaxWidth = { 200 }, description = { "" }, descriptionMaxWidth = { 300 }, + schedMeetingNotification = null, dateText = { "" }, isNew = true, backgroundColor = { "#D3D3D3" }, separatorMargin = { 0 }, - onClick = {}, - chatDateText = { null }, - recurringSchedMeeting = null, - ) + ) {} val newNotification = initialNotification.copy(title = {"New title"}) whenever(notificationMapper(initialAlert)).thenReturn(initialNotification) whenever(notificationMapper(newAlert)).thenReturn(newNotification) diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/notification/view/NotificationViewTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/notification/view/NotificationViewTest.kt index 0a4c4d3e91d..9d1f847f13e 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/notification/view/NotificationViewTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/notification/view/NotificationViewTest.kt @@ -44,14 +44,13 @@ class NotificationViewTest { titleMaxWidth = { 200 }, description = { "xyz@gmail.com is now a contact" }, descriptionMaxWidth = { 300 }, + schedMeetingNotification = null, dateText = { "11 October 2022 6:46 pm" }, isNew = true, backgroundColor = { "#D3D3D3" }, separatorMargin = { 0 }, - onClick = {}, - chatDateText = { null }, - recurringSchedMeeting = null, - )))) + ) {} + ))) } composeRule.onNodeWithTag("NotificationListView").assertIsDisplayed() } @@ -69,14 +68,12 @@ class NotificationViewTest { titleMaxWidth = { 200 }, description = { "xyz@gmail.com is now a contact" }, descriptionMaxWidth = { 300 }, + schedMeetingNotification = null, dateText = { "11 October 2022 6:46 pm" }, isNew = true, backgroundColor = { "#D3D3D3" }, separatorMargin = { 0 }, - onClick = {}, - chatDateText = { null }, - recurringSchedMeeting = null, - ), + ) {}, Notification( sectionTitle = { "INCOMING SHARES" }, sectionColour = R.color.orange_400_orange_300, @@ -86,14 +83,13 @@ class NotificationViewTest { titleMaxWidth = { 200 }, description = { "Access to the folders shared by xyz@gmail.com were removed" }, descriptionMaxWidth = { 300 }, + schedMeetingNotification = null, dateText = { "13 May 2022 5:46 am" }, isNew = true, backgroundColor = { "#D3D3D3" }, separatorMargin = { 0 }, - onClick = {}, - chatDateText = { null }, - recurringSchedMeeting = null, - )))) + ) {} + ))) } composeRule.onAllNodesWithTag("NotificationItemView").assertCountEquals(2) } @@ -110,13 +106,13 @@ class NotificationViewTest { titleMaxWidth = { 200 }, description = { "xyz@gmail.com is now a contact" }, descriptionMaxWidth = { 300 }, + schedMeetingNotification = null, dateText = { "11 October 2022 6:46 pm" }, isNew = true, backgroundColor = { "#D3D3D3" }, separatorMargin = { 0 }, - onClick = {}, - chatDateText = { null }, - recurringSchedMeeting = null,)))) + ) {} + ))) } composeRule.onNodeWithTag("SectionTitle", useUnmergedTree = true).assertIsDisplayed() } @@ -133,13 +129,13 @@ class NotificationViewTest { titleMaxWidth = { 200 }, description = { "xyz@gmail.com is now a contact" }, descriptionMaxWidth = { 300 }, + schedMeetingNotification = null, dateText = { "11 October 2022 6:46 pm" }, isNew = true, backgroundColor = { "#D3D3D3" }, separatorMargin = { 0 }, - onClick = {}, - chatDateText = { null }, - recurringSchedMeeting = null,)))) + ) {} + ))) } composeRule.onNodeWithTag("Title").assertIsDisplayed() } @@ -156,13 +152,13 @@ class NotificationViewTest { titleMaxWidth = { 200 }, description = { "xyz@gmail.com is now a contact" }, descriptionMaxWidth = { 300 }, + schedMeetingNotification = null, dateText = { "11 October 2022 6:46 pm" }, isNew = true, backgroundColor = { "#D3D3D3" }, separatorMargin = { 0 }, - onClick = {}, - chatDateText = { null }, - recurringSchedMeeting = null,)))) + ) {} + ))) } composeRule.onNodeWithTag("Description").assertIsDisplayed() } @@ -179,13 +175,13 @@ class NotificationViewTest { titleMaxWidth = { 200 }, description = { null }, descriptionMaxWidth = { 300 }, + schedMeetingNotification = null, dateText = { "11 October 2022 6:46 pm" }, isNew = true, backgroundColor = { "#D3D3D3" }, separatorMargin = { 0 }, - onClick = {}, - chatDateText = { null }, - recurringSchedMeeting = null,)))) + ) {} + ))) } composeRule.onNodeWithTag("Description").assertDoesNotExist() } @@ -202,13 +198,13 @@ class NotificationViewTest { titleMaxWidth = { 200 }, description = { "xyz@gmail.com is now a contact" }, descriptionMaxWidth = { 300 }, + schedMeetingNotification = null, dateText = { "11 October 2022 6:46 pm" }, isNew = true, backgroundColor = { "#D3D3D3" }, separatorMargin = { 0 }, - onClick = {}, - chatDateText = { null }, - recurringSchedMeeting = null,)))) + ) {} + ))) } composeRule.onNodeWithTag("DateText", useUnmergedTree = true).assertIsDisplayed() } @@ -225,13 +221,13 @@ class NotificationViewTest { titleMaxWidth = { 200 }, description = { "xyz@gmail.com is now a contact" }, descriptionMaxWidth = { 300 }, + schedMeetingNotification = null, dateText = { "11 October 2022 6:46 pm" }, isNew = true, backgroundColor = { "#D3D3D3" }, separatorMargin = { 0 }, - onClick = {}, - chatDateText = { null }, - recurringSchedMeeting = null,)))) + ) {} + ))) } composeRule.onNodeWithTag("IsNew", useUnmergedTree = true).assertIsDisplayed() } @@ -248,13 +244,13 @@ class NotificationViewTest { titleMaxWidth = { 200 }, description = { "xyz@gmail.com is now a contact" }, descriptionMaxWidth = { 300 }, + schedMeetingNotification = null, dateText = { "11 October 2022 6:46 pm" }, isNew = false, backgroundColor = { "#D3D3D3" }, separatorMargin = { 0 }, - onClick = {}, - chatDateText = { null }, - recurringSchedMeeting = null,)))) + ) {} + ))) } composeRule.onNodeWithTag("IsNew").assertDoesNotExist() } diff --git a/build.gradle b/build.gradle index 709c8481e7d..ffe50d09c99 100644 --- a/build.gradle +++ b/build.gradle @@ -69,7 +69,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230222.010407-rel' + megaSdkVersion = '20230226.232049-dev' // App dependencies accompanistLayoutVersion = '0.24.13-rc' diff --git a/data/src/main/java/mega/privacy/android/data/mapper/UserAlertMapper.kt b/data/src/main/java/mega/privacy/android/data/mapper/UserAlertMapper.kt index fe380f452c0..226d82475f2 100644 --- a/data/src/main/java/mega/privacy/android/data/mapper/UserAlertMapper.kt +++ b/data/src/main/java/mega/privacy/android/data/mapper/UserAlertMapper.kt @@ -392,7 +392,7 @@ private suspend fun MegaUserAlert.getUpdatedMeetingAlert( scheduledMeeting = meeting, ) } else { - val dateTimeChanges = getDateTimeChanges() + val dateTimeChanges = getDateTimeChanges(updatedOccurrence) UpdatedScheduledMeetingDateTimeAlert( id = id, seen = seen, @@ -592,13 +592,15 @@ private fun getChildNodes(megaUserAlert: MegaUserAlert) = private fun MegaUserAlert.getScheduledMeetingChanges(): List = ScheduledMeetingChangeType.values().filter { hasSchedMeetingChanged(it.value) } -private fun Long?.isValid() = this != null && this != -1L - -private fun MegaUserAlert.getDateTimeChanges(): Pair { +private fun MegaUserAlert.getDateTimeChanges( + occurrence: ChatScheduledMeetingOccurr? = null, +): Pair { var hasDateChanged = false var hasTimeChanged = false val oldStartDateTime = updatedStartDate?.get(0)?.toZonedDateTime() + ?: getNumber(0).toZonedDateTime() val newStartDateTime = updatedStartDate?.get(1)?.toZonedDateTime() + ?: occurrence?.startDateTime?.toZonedDateTime() val oldEndDateTime = updatedEndDate?.get(0)?.toZonedDateTime() val newEndDateTime = updatedEndDate?.get(1)?.toZonedDateTime() @@ -618,5 +620,9 @@ private fun MegaUserAlert.getDateTimeChanges(): Pair { return hasDateChanged to hasTimeChanged } -private fun Long.toZonedDateTime(): ZonedDateTime = - ZonedDateTime.ofInstant(Instant.ofEpochSecond(this), ZoneOffset.UTC) +private fun Long?.isValid() = this != null && this != -1L + +private fun Long.toZonedDateTime(): ZonedDateTime? = + takeIf(Long::isValid)?.let { timeStamp -> + ZonedDateTime.ofInstant(Instant.ofEpochSecond(timeStamp), ZoneOffset.UTC) + } diff --git a/data/src/main/java/mega/privacy/android/data/repository/DefaultChatRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/DefaultChatRepository.kt index 2b8479567c5..2159ed961a7 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/DefaultChatRepository.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/DefaultChatRepository.kt @@ -56,6 +56,10 @@ import nz.mega.sdk.MegaError import nz.mega.sdk.MegaRequest import nz.mega.sdk.MegaUser import timber.log.Timber +import java.time.Instant +import java.time.ZoneOffset +import java.time.ZonedDateTime +import java.time.temporal.ChronoUnit import javax.inject.Inject import kotlin.coroutines.Continuation import kotlin.coroutines.resume @@ -280,24 +284,35 @@ internal class DefaultChatRepository @Inject constructor( var fetch: Boolean do { - val newOccurrences = runCatching { - fetchScheduledMeetingOccurrencesByChat(chatId, lastTimeStamp) - }.getOrNull() - if (newOccurrences.isNullOrEmpty()) { - fetch = false - } else { + val newOccurrences = fetchScheduledMeetingOccurrencesByChat(chatId, lastTimeStamp) + if (newOccurrences.isNotEmpty()) { occurrences.apply { addAll(newOccurrences) sortBy(ChatScheduledMeetingOccurr::startDateTime) } lastTimeStamp = newOccurrences.last().startDateTime!! fetch = occurrences.size < count + } else { + fetch = false } } while (fetch) occurrences.toList() } + override suspend fun getNextScheduledMeetingOccurrence(chatId: Long): ChatScheduledMeetingOccurr? = + withContext(ioDispatcher) { + val now = Instant.now().atZone(ZoneOffset.UTC) + fetchScheduledMeetingOccurrencesByChat( + chatId, + now.minus(1L, ChronoUnit.HALF_DAYS).toEpochSecond() + ).firstOrNull { occurr -> + !occurr.isCancelled && occurr.parentSchedId == megaChatApiGateway.getChatInvalidHandle() + && (occurr.startDateTime?.toZonedDateTime()?.isAfter(now) == true + || occurr.endDateTime?.toZonedDateTime()?.isAfter(now) == true) + } + } + override suspend fun inviteToChat(chatId: Long, contactsData: List) = withContext(ioDispatcher) { contactsData.forEach { email -> @@ -526,4 +541,7 @@ internal class DefaultChatRepository @Inject constructor( localStorageGateway.setChatSettings(ChatSettings()) } } + + private fun Long.toZonedDateTime(): ZonedDateTime = + ZonedDateTime.ofInstant(Instant.ofEpochSecond(this), ZoneOffset.UTC) } diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/entity/UserAlert.kt b/domain/src/main/kotlin/mega/privacy/android/domain/entity/UserAlert.kt index d1eebae665e..068c93c0647 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/entity/UserAlert.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/entity/UserAlert.kt @@ -59,7 +59,7 @@ sealed interface IncomingShareAlert { sealed interface ScheduledMeetingAlert { val chatId: Long val title: String - val email: String + val email: String? val startDate: Long? val endDate: Long? val isRecurring: Boolean @@ -690,7 +690,7 @@ data class UpdatedScheduledMeetingDateTimeAlert( override val isOwnChange: Boolean, override val chatId: Long, override val title: String, - override val email: String, + override val email: String?, override val startDate: Long?, override val endDate: Long?, override val isRecurring: Boolean, @@ -754,7 +754,7 @@ data class UpdatedScheduledMeetingFieldsAlert( override val isOwnChange: Boolean, override val chatId: Long, override val title: String, - override val email: String, + override val email: String?, override val startDate: Long?, override val endDate: Long?, override val isRecurring: Boolean, diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/entity/chat/ChatScheduledMeeting.kt b/domain/src/main/kotlin/mega/privacy/android/domain/entity/chat/ChatScheduledMeeting.kt index f95e3eca70f..a3b67c05e2d 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/entity/chat/ChatScheduledMeeting.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/entity/chat/ChatScheduledMeeting.kt @@ -33,7 +33,7 @@ data class ChatScheduledMeeting constructor( val endDateTime: Long? = null, val title: String? = "", val description: String? = "", - val attributes: String?, + val attributes: String? = "", val overrides: Long? = null, val flags: ChatScheduledFlags? = null, val rules: ChatScheduledRules? = null, diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/repository/ChatRepository.kt b/domain/src/main/kotlin/mega/privacy/android/domain/repository/ChatRepository.kt index ad071b74606..c5bd73ca774 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/repository/ChatRepository.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/repository/ChatRepository.kt @@ -147,7 +147,7 @@ interface ChatRepository { /** * Get a list of all scheduled meeting occurrences for a chatroom * - * @param chatId MegaChatHandle that identifies a chat room + * @param chatId MegaChatHandle that identifies a chat room * @param since Timestamp from which API will generate more occurrences * @return The list of scheduled meetings occurrences. */ @@ -156,6 +156,14 @@ interface ChatRepository { since: Long, ): List + /** + * Get next available scheduled meeting occurrence given the current time + * + * @param chatId MegaChatHandle that identifies a chat room + * @return ChatScheduledMeetingOccurr + */ + suspend fun getNextScheduledMeetingOccurrence(chatId: Long): ChatScheduledMeetingOccurr? + /** * Invite contacts to chat. * diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultGetMeetings.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultGetMeetings.kt index f9ee36c49c7..427066b0153 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultGetMeetings.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/DefaultGetMeetings.kt @@ -11,16 +11,11 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import mega.privacy.android.domain.entity.ChatRoomLastMessage import mega.privacy.android.domain.entity.chat.ChatListItemChanges -import mega.privacy.android.domain.entity.chat.ChatScheduledMeetingOccurr import mega.privacy.android.domain.entity.chat.CombinedChatRoom import mega.privacy.android.domain.entity.chat.MeetingRoomItem import mega.privacy.android.domain.entity.meeting.OccurrenceFrequencyType import mega.privacy.android.domain.repository.ChatRepository import mega.privacy.android.domain.repository.GetMeetingsRepository -import java.time.Instant -import java.time.ZoneOffset -import java.time.ZonedDateTime -import java.time.temporal.ChronoUnit import javax.inject.Inject /** @@ -66,7 +61,7 @@ class DefaultGetMeetings @Inject constructor( private suspend fun MutableList.addScheduledMeetings(mutex: Mutex): Flow> = flow { toList().forEach { item -> - getScheduledMeetingItem(item)?.let { updatedItem -> + item.getScheduledMeetingItem()?.let { updatedItem -> mutex.withLock { val newIndex = indexOfFirst { updatedItem.chatId == it.chatId } if (newIndex != -1) { @@ -151,7 +146,7 @@ class DefaultGetMeetings @Inject constructor( ?.let { getMeetingsRepository.getUpdatedMeetingItem(it) } ?: return@apply - val newUpdatedItem = getScheduledMeetingItem(newItem) ?: newItem + val newUpdatedItem = newItem.getScheduledMeetingItem() ?: newItem if (currentItemIndex != -1) { val currentItem = get(currentItemIndex) if (currentItem != newUpdatedItem) { @@ -180,7 +175,7 @@ class DefaultGetMeetings @Inject constructor( } val currentItem = get(currentItemIndex) - getScheduledMeetingItem(currentItem)?.let { updatedItem -> + currentItem.getScheduledMeetingItem()?.let { updatedItem -> mutex.withLock { val newIndex = indexOfFirst { updatedItem.chatId == it.chatId } if (newIndex != -1) { @@ -201,19 +196,11 @@ class DefaultGetMeetings @Inject constructor( } } - private suspend fun CombinedChatRoom.toMeetingRoomItem(): MeetingRoomItem = - meetingRoomMapper.invoke( - this, - chatRepository::isChatNotifiable, - chatRepository::isChatLastMessageGeolocation - ) - - private suspend fun getScheduledMeetingItem(item: MeetingRoomItem): MeetingRoomItem? = - chatRepository.getScheduledMeetingsByChat(item.chatId) - ?.firstOrNull() + private suspend fun MeetingRoomItem.getScheduledMeetingItem(): MeetingRoomItem? = + chatRepository.getScheduledMeetingsByChat(chatId)?.firstOrNull() ?.takeIf { !it.isCanceled } ?.let { schedMeeting -> - val isPending = item.isActive && schedMeeting.isPending() + val isPending = isActive && schedMeeting.isPending() val isRecurringDaily = schedMeeting.rules?.freq == OccurrenceFrequencyType.Daily val isRecurringWeekly = schedMeeting.rules?.freq == OccurrenceFrequencyType.Weekly val isRecurringMonthly = schedMeeting.rules?.freq == OccurrenceFrequencyType.Monthly @@ -221,13 +208,14 @@ class DefaultGetMeetings @Inject constructor( var endTimestamp = schedMeeting.endDateTime if (isPending && schedMeeting.rules != null) { - getNextOccurrence(item.chatId)?.let { nextOccurrence -> - startTimestamp = nextOccurrence.startDateTime - endTimestamp = nextOccurrence.endDateTime - } ?: return null + runCatching { chatRepository.getNextScheduledMeetingOccurrence(chatId) } + .getOrNull()?.let { nextOccurrence -> + startTimestamp = nextOccurrence.startDateTime + endTimestamp = nextOccurrence.endDateTime + } ?: return null } - item.copy( + copy( schedId = schedMeeting.schedId, scheduledStartTimestamp = startTimestamp, scheduledEndTimestamp = endTimestamp, @@ -238,6 +226,13 @@ class DefaultGetMeetings @Inject constructor( ) } + private suspend fun CombinedChatRoom.toMeetingRoomItem(): MeetingRoomItem = + meetingRoomMapper.invoke( + this, + chatRepository::isChatNotifiable, + chatRepository::isChatLastMessageGeolocation + ) + private suspend fun MutableList.sortMeetings(mutex: Mutex) { mutex.withLock { sortWith { firstMeeting, secondMeeting -> @@ -264,20 +259,4 @@ class DefaultGetMeetings @Inject constructor( } } } - - private suspend fun getNextOccurrence(chatId: Long): ChatScheduledMeetingOccurr? = - runCatching { - val now = Instant.now().atZone(ZoneOffset.UTC) - chatRepository.fetchScheduledMeetingOccurrencesByChat( - chatId, - now.minus(1L, ChronoUnit.HALF_DAYS).toEpochSecond() - ).firstOrNull { occurr -> - !occurr.isCancelled && occurr.parentSchedId == -1L && - (occurr.startDateTime?.toZonedDateTime()?.isAfter(now) == true - || occurr.endDateTime?.toZonedDateTime()?.isAfter(now) == true) - } - }.getOrNull() - - private fun Long.toZonedDateTime(): ZonedDateTime = - ZonedDateTime.ofInstant(Instant.ofEpochSecond(this), ZoneOffset.UTC) } From 6d25dda26b7e4bf98cfc2e06af01952dca05e9a6 Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Tue, 28 Feb 2023 11:30:00 +0800 Subject: [PATCH 127/334] Revert the megaSdkVersion to "20230222.010407-rel" --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ffe50d09c99..709c8481e7d 100644 --- a/build.gradle +++ b/build.gradle @@ -69,7 +69,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230226.232049-dev' + megaSdkVersion = '20230222.010407-rel' // App dependencies accompanistLayoutVersion = '0.24.13-rc' From 1f642cc2f77bc9bfa025f6a61f57a7280310cb79 Mon Sep 17 00:00:00 2001 From: Gonzalo Toledano Date: Tue, 28 Feb 2023 16:57:56 +1300 Subject: [PATCH 128/334] MEET-2020 Scheduled meetings mobile release support --- .../android/app/meeting/list/adapter/MeetingsAdapter.kt | 4 ++-- .../presentation/notification/view/NotificationItemView.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/meeting/list/adapter/MeetingsAdapter.kt b/app/src/main/java/mega/privacy/android/app/meeting/list/adapter/MeetingsAdapter.kt index 57a1bc0cf4a..d99084d751f 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/list/adapter/MeetingsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/list/adapter/MeetingsAdapter.kt @@ -100,7 +100,7 @@ class MeetingsAdapter constructor( return this?.map(MeetingAdapterItem::Data) } - val itemsWithHeader = mutableListOf() + val itemsWithHeader = mutableSetOf() forEachIndexed { index, item -> val previousItem = getOrNull(index - 1) when { @@ -113,7 +113,7 @@ class MeetingsAdapter constructor( } itemsWithHeader.add(MeetingAdapterItem.Data(item)) } - return itemsWithHeader + return itemsWithHeader.toList() } private fun isSameDay(timeStampA: Long?, timeStampB: Long?): Boolean = diff --git a/app/src/main/java/mega/privacy/android/app/presentation/notification/view/NotificationItemView.kt b/app/src/main/java/mega/privacy/android/app/presentation/notification/view/NotificationItemView.kt index 7a0ee570c68..7b7a5e0f56e 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/notification/view/NotificationItemView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/notification/view/NotificationItemView.kt @@ -73,7 +73,7 @@ internal fun NotificationItemView( NotificationDescription(notification) } - if (notification.schedMeetingNotification != null) { + if (notification.schedMeetingNotification?.scheduledMeeting != null) { NotificationSchedMeetingView(notification.schedMeetingNotification) } NotificationDate(notification) From b817c633722afa107ed946c56670749adf1441ad Mon Sep 17 00:00:00 2001 From: Yenel Date: Tue, 28 Feb 2023 10:33:59 +0100 Subject: [PATCH 129/334] T4575104 Available offline options should work correctly --- .../java/mega/privacy/android/app/utils/OfflineUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/mega/privacy/android/app/utils/OfflineUtils.java b/app/src/main/java/mega/privacy/android/app/utils/OfflineUtils.java index 49a1c0b9e74..af5fba2e5a2 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/OfflineUtils.java +++ b/app/src/main/java/mega/privacy/android/app/utils/OfflineUtils.java @@ -331,6 +331,10 @@ private static String getOfflinePath(String path, MegaOffline offlineNode) { public static File getOfflineParentFile(Context context, int from, MegaNode node, MegaApiAndroid megaApi) { String path = context.getFilesDir().getAbsolutePath() + File.separator; + if (megaApi == null) { + megaApi = MegaApplication.getInstance().getMegaApi(); + } + switch (from) { case FROM_INCOMING_SHARES: { path = path + OFFLINE_DIR + File.separator + findIncomingParentHandle(node, megaApi); From 8346324ce1e04202ae351bef51e4bdce34d1e350 Mon Sep 17 00:00:00 2001 From: Raquel Garcia Chico Date: Wed, 1 Mar 2023 02:33:22 +1300 Subject: [PATCH 130/334] T4575148 The user should be able to send a private message --- .../app/main/megachat/ChatActivity.java | 15 +++++++++++---- .../app/presentation/chat/ChatViewModel.kt | 18 ++++++++++++++++++ .../app/presentation/chat/model/ChatState.kt | 2 ++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java b/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java index 703c11142c7..50008912c76 100644 --- a/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java @@ -2095,7 +2095,6 @@ public void initAfterIntent(Intent newIntent, Bundle savedInstanceState) { composite.clear(); checkChatChanges(); - myUserHandle = megaChatApi.getMyUserHandle(); if (savedInstanceState != null) { @@ -2180,6 +2179,7 @@ public void initAfterIntent(Intent newIntent, Bundle savedInstanceState) { } } initEmptyScreen(text); + initAndShowChat(); } } } else { @@ -2294,6 +2294,7 @@ private boolean initChat() { megaChatApi.closeChatRoom(idChat, this); if (megaChatApi.openChatRoom(idChat, this)) { + viewModel.setChatInitialised(true); MegaApplication.setClosedChat(false); return true; } @@ -8376,6 +8377,7 @@ public void onRequestTemporaryError(MegaChatApiJava api, MegaChatRequest request @Override protected void onStop() { super.onStop(); + viewModel.setChatInitialised(false); } private void cleanBuffers() { @@ -8390,12 +8392,17 @@ private void cleanBuffers() { } } - @Override protected void onStart() { super.onStart(); - cleanBuffers(); - initAndShowChat(); + if(!viewModel.isChatInitialised()) { + cleanBuffers(); + + if (!initChat()) { + return; + } + loadHistory(); + } } @Override diff --git a/app/src/main/java/mega/privacy/android/app/presentation/chat/ChatViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/chat/ChatViewModel.kt index 29ce3693f52..9f6edd40182 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/chat/ChatViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/chat/ChatViewModel.kt @@ -130,6 +130,24 @@ class ChatViewModel @Inject constructor( } } + /** + * Set if the chat has been initialised. + * + * @param value True, if the chat has been initialised. False, otherwise. + */ + fun setChatInitialised(value: Boolean) { + _state.update { it.copy(isChatInitialised = value) } + } + + /** + * Check if the chat has been initialised. + * + * @return True, if the chat has been initialised. False, otherwise. + */ + fun isChatInitialised(): Boolean { + return state.value.isChatInitialised + } + /** * Answers a call. * diff --git a/app/src/main/java/mega/privacy/android/app/presentation/chat/model/ChatState.kt b/app/src/main/java/mega/privacy/android/app/presentation/chat/model/ChatState.kt index 9c1d1b9d5c9..cd35a7fc863 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/chat/model/ChatState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/chat/model/ChatState.kt @@ -5,8 +5,10 @@ package mega.privacy.android.app.presentation.chat.model * * @property error String resource id for showing an error. * @property isCallAnswered Handle when a call is answered. + * @property isChatInitialised True, if the chat is initialised. False, if not. */ data class ChatState( val error: Int? = null, val isCallAnswered: Boolean = false, + val isChatInitialised: Boolean = false, ) \ No newline at end of file From 49762d868453cc8709c739ce9265b9590e90f750 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yenel=20Rodr=C3=ADguez=20Hern=C3=A1ndez?= Date: Wed, 1 Mar 2023 14:14:44 +1300 Subject: [PATCH 131/334] T4575001 User should be able to upload the filtered result from chat (cherry picked from commit d3ce63d2641eb99da16b5076db1d0a585fcce90a) --- .../android/app/main/CloudDriveExplorerFragment.kt | 13 +++++++++---- .../app/main/IncomingSharesExplorerFragment.kt | 5 ++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/CloudDriveExplorerFragment.kt b/app/src/main/java/mega/privacy/android/app/main/CloudDriveExplorerFragment.kt index 52336ff50d3..2108dec91c4 100644 --- a/app/src/main/java/mega/privacy/android/app/main/CloudDriveExplorerFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/main/CloudDriveExplorerFragment.kt @@ -211,12 +211,17 @@ class CloudDriveExplorerFragment : RotatableFragment(), CheckScrollInterface, Se override fun reselectUnHandledSingleItem(position: Int) {} - override fun checkScroll() = + override fun checkScroll() { + if (!isAdded) return + (recyclerView.canScrollHorizontally(SCROLLING_UP_DIRECTION) || (adapter.multipleSelected)) .let { elevate -> - (requireActivity() as FileExplorerActivity).changeActionBarElevation(elevate, - CLOUD_FRAGMENT) + (requireActivity() as FileExplorerActivity).changeActionBarElevation( + elevate, + CLOUD_FRAGMENT + ) } + } private fun showSortByPanel() = (requireActivity() as FileExplorerActivity).showSortByPanel() @@ -765,7 +770,7 @@ class CloudDriveExplorerFragment : RotatableFragment(), CheckScrollInterface, Se * @param searchString search strings */ fun search(searchString: String?) { - if (searchString == null && !shouldResetNodes) { + if (searchString == null || !shouldResetNodes) { return } if (parentHandle == INVALID_HANDLE) diff --git a/app/src/main/java/mega/privacy/android/app/main/IncomingSharesExplorerFragment.kt b/app/src/main/java/mega/privacy/android/app/main/IncomingSharesExplorerFragment.kt index baaefa3bbfc..0bd5bc60f24 100644 --- a/app/src/main/java/mega/privacy/android/app/main/IncomingSharesExplorerFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/main/IncomingSharesExplorerFragment.kt @@ -189,11 +189,14 @@ class IncomingSharesExplorerFragment : RotatableFragment(), CheckScrollInterface override fun reselectUnHandledSingleItem(position: Int) {} - override fun checkScroll() = + override fun checkScroll() { + if (!isAdded) return + (recyclerView.canScrollHorizontally(SCROLLING_UP_DIRECTION) || adapter.multipleSelected) .let { elevate -> fileExplorerActivity.changeActionBarElevation(elevate, INCOMING_FRAGMENT) } + } /** * Shows the Sort by panel. From 022d517041c5b39720445670f2a9f7ca4e607662 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 28 Feb 2023 12:31:21 +0530 Subject: [PATCH 132/334] cherry pick aea17e44 --- .../app/main/adapters/MegaNodeAdapter.java | 1 - .../outgoing/OutgoingSharesViewModel.kt | 7 ++- .../manager/ManagerViewModelTest.kt | 48 ++++++++++++++++--- .../incoming/IncomingSharesViewModelTest.kt | 2 +- .../outgoing/OutgoingSharesViewModelTest.kt | 2 +- .../android/data/facade/MegaApiFacade.kt | 38 ++++++++------- .../android/data/mapper/MegaShareMapper.kt | 3 +- .../data/repository/MegaNodeRepository.kt | 15 ++---- .../data/repository/MegaNodeRepositoryImpl.kt | 32 ++++++------- .../android/domain/entity/ShareData.kt | 2 + 10 files changed, 90 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index db904fab18e..00b71400c09 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1100,7 +1100,6 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } } else if (type == OUTGOING_SHARES_ADAPTER) { - holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); boolean hasUnverifiedNodes = !unverifiedOutgoingNodeHandles.isEmpty() diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index cd932bf01e5..794e8b9daf9 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -56,11 +56,14 @@ class OutgoingSharesViewModel @Inject constructor( viewModelScope.launch { val unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder) .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + .filter { shareData -> !shareData.isVerified } val handles = unverifiedOutgoingShares .map { shareData -> shareData.nodeHandle } _state.update { - it.copy(unverifiedOutgoingShares = unverifiedOutgoingShares, - unVerifiedOutgoingNodeHandles = handles) + it.copy( + unverifiedOutgoingShares = unverifiedOutgoingShares, + unVerifiedOutgoingNodeHandles = handles + ) } } } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index 2c8de7a3baa..a9647f50782 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -7,6 +7,7 @@ import com.google.common.truth.Truth.assertThat import com.jraska.livedata.test import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter @@ -29,6 +30,8 @@ import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.contacts.ContactRequest import mega.privacy.android.domain.entity.contacts.ContactRequestStatus import mega.privacy.android.domain.entity.node.NodeUpdate +import mega.privacy.android.domain.entity.verification.UnVerified +import mega.privacy.android.domain.entity.verification.VerificationStatus import mega.privacy.android.domain.usecase.CheckCameraUpload import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetFeatureFlagValue @@ -71,9 +74,36 @@ class ManagerViewModelTest { private val checkCameraUpload = mock() private val getCloudSortOrder = mock() private val monitorConnectivity = mock() - private val getFeatureFlagValue = mock() - private val getUnverifiedOutgoingShares = mock() - private val getUnverifiedIncomingShares = mock() + private val getFeatureFlagValue = + mock { onBlocking { invoke(any()) }.thenReturn(false) } + private val shareDataList = listOf( + ShareData("user", 8766L, 0, 987654678L, true, false), + ShareData("user", 8766L, 0, 987654678L, true, false) + ) + private val getUnverifiedOutgoingShares = mock { + onBlocking { + invoke( + any() + ) + }.thenReturn( + shareDataList + ) + } + private val getUnverifiedIncomingShares = mock { + onBlocking { + invoke( + any() + ) + }.thenReturn(shareDataList) + } + private val initialFinishActivityValue = false + private val monitorFinishActivity = MutableStateFlow(initialFinishActivityValue) + private val monitorVerificationStatus = MutableStateFlow( + UnVerified( + canRequestUnblockSms = false, + canRequestOptInVerification = false + ) + ) @get:Rule var instantExecutorRule = InstantTaskExecutorRule() @@ -327,10 +357,14 @@ class ManagerViewModelTest { @Test fun `test that pending actions count is not null`() = runTest { - val shareData = ShareData("user", 8766L, 0, 987654678L, true) - val shareData1 = ShareData("user", 8766L, 0, 987654678L, true) - whenever(getUnverifiedIncomingShares(getCloudSortOrder())).thenReturn(listOf(shareData, - shareData1)) + val shareData = ShareData("user", 8766L, 0, 987654678L, true, false) + val shareData1 = ShareData("user", 8766L, 0, 987654678L, true, false) + whenever(getUnverifiedIncomingShares(getCloudSortOrder())).thenReturn( + listOf( + shareData, + shareData1 + ) + ) setUnderTest() underTest.state.map { it.pendingActionsCount }.distinctUntilChanged().test { assertThat(awaitItem()).isEqualTo(2) diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 4819018e207..73ac6ceb819 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -57,7 +57,7 @@ class IncomingSharesViewModelTest { var instantExecutorRule = InstantTaskExecutorRule() private val getUnverifiedIncomingShares = mock { - val shareData = ShareData("user", 8766L, 0, 987654678L, true) + val shareData = ShareData("user", 8766L, 0, 987654678L, true, false) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index d9f187e04eb..6baad190340 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -52,7 +52,7 @@ class OutgoingSharesViewModelTest { var instantExecutorRule = InstantTaskExecutorRule() private val getUnverifiedOutgoingShares = mock { - val shareData = ShareData("user", 8766L, 0, 987654678L, true) + val shareData = ShareData("user", 8766L, 0, 987654678L, true, false) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index 13214a7715f..403bc17ad40 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -675,8 +675,9 @@ internal class MegaApiFacade @Inject constructor( override fun createSetElement(sid: Long, node: Long, listener: MegaRequestListenerInterface) = megaApi.createSetElement(sid, node, "", listener) - override suspend fun removeSetElement(sid: Long, eid: Long) = + override suspend fun removeSetElement(sid: Long, eid: Long) { megaApi.removeSetElement(sid, eid) + } override suspend fun getSets(): MegaSetList = megaApi.sets @@ -915,23 +916,6 @@ internal class MegaApiFacade @Inject constructor( override suspend fun cancelTransfers(direction: Int) = megaApi.cancelTransfers(direction) - override suspend fun getUnverifiedIncomingShares(order: Int): List = - megaApi.getUnverifiedIncomingShares(order) - - override suspend fun getUnverifiedOutgoingShares(order: Int): List = - megaApi.getUnverifiedOutgoingShares(order) - - override fun openShareDialog( - megaNode: MegaNode, - listener: MegaRequestListenerInterface, - ) = megaApi.openShareDialog(megaNode, listener) - - override fun upgradeSecurity(listener: MegaRequestListenerInterface) = - megaApi.upgradeSecurity(listener) - - @Deprecated("This API is for testing purpose, will be deleted later") - override fun setSecureFlag(enable: Boolean) = megaApi.setSecureFlag(enable) - override suspend fun getVerifiedPhoneNumber(): String? = megaApi.smsVerifiedPhoneNumber() override fun verifyPhoneNumber(pin: String, listener: MegaRequestListenerInterface) = @@ -989,5 +973,23 @@ internal class MegaApiFacade @Inject constructor( ) } + override suspend fun getUnverifiedIncomingShares(order: Int): List = + megaApi.getUnverifiedIncomingShares(order) + + override suspend fun getUnverifiedOutgoingShares(order: Int): List = + megaApi.getOutShares(order) + + override fun openShareDialog( + megaNode: MegaNode, + listener: MegaRequestListenerInterface, + ) = megaApi.openShareDialog(megaNode, listener) + + override fun upgradeSecurity(listener: MegaRequestListenerInterface) = + megaApi.upgradeSecurity(listener) + + @Deprecated("This API is for testing purpose, will be deleted later") + override fun setSecureFlag(enable: Boolean) = megaApi.setSecureFlag(enable) + override suspend fun getSmsAllowedState() = megaApi.smsAllowedState() + } diff --git a/data/src/main/java/mega/privacy/android/data/mapper/MegaShareMapper.kt b/data/src/main/java/mega/privacy/android/data/mapper/MegaShareMapper.kt index cc812075e19..f72dbde8cc6 100644 --- a/data/src/main/java/mega/privacy/android/data/mapper/MegaShareMapper.kt +++ b/data/src/main/java/mega/privacy/android/data/mapper/MegaShareMapper.kt @@ -13,5 +13,6 @@ internal fun toShareModel(share: MegaShare) = ShareData( isPending = share.isPending, timeStamp = share.timestamp, access = share.access, - nodeHandle = share.nodeHandle + nodeHandle = share.nodeHandle, + isVerified = share.isVerified, ) \ No newline at end of file diff --git a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt index 86f4cd7983c..ac27718abba 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt @@ -286,11 +286,11 @@ interface MegaNodeRepository { suspend fun getUnverifiedIncomingShares(order: SortOrder): List /** - * Creates a new share key for the node if there is no share key already created. + * Provides Unverified outgoing shares count from SDK * - * @param megaNode : [MegaNode] object which needs to be shared + * @return List of [ShareData] */ - suspend fun getUnverifiedOutgoingShares(): Int + suspend fun getUnverifiedOutgoingShares(order: SortOrder): List /** * Provides searched nodes InShares from query @@ -340,17 +340,8 @@ interface MegaNodeRepository { megaCancelToken: MegaCancelToken, ): List - /** - * Provides Unverified outgoing shares count from SDK - * - * @return Integer count - * @return List of [ShareData] - */ - suspend fun getUnverifiedOutgoingShares(order: SortOrder): List - /** * Creates a new share key for the node if there is no share key already created. - * * @param megaNode : [MegaNode] object which needs to be shared */ suspend fun openShareDialog(megaNode: MegaNode) diff --git a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepositoryImpl.kt b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepositoryImpl.kt index 70d87dfdb48..6e9e93002b6 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepositoryImpl.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepositoryImpl.kt @@ -336,12 +336,11 @@ internal class MegaNodeRepositoryImpl @Inject constructor( override suspend fun getUnverifiedOutgoingShares(order: SortOrder): List = withContext(ioDispatcher) { - megaApiGateway.getUnverifiedOutgoingShares(sortOrderIntMapper(order)).map { + megaApiGateway.getOutgoingSharesNode(sortOrderIntMapper(order)).map { megaShareMapper(it) } } - override suspend fun openShareDialog(megaNode: MegaNode) = withContext(ioDispatcher) { suspendCancellableCoroutine { continuation -> val listener = continuation.getRequestListener { return@getRequestListener } @@ -352,21 +351,6 @@ internal class MegaNodeRepositoryImpl @Inject constructor( } } - override suspend fun upgradeSecurity() = withContext(ioDispatcher) { - suspendCancellableCoroutine { continuation -> - val listener = continuation.getRequestListener { return@getRequestListener } - megaApiGateway.upgradeSecurity(listener) - continuation.invokeOnCancellation { - megaApiGateway.removeRequestListener(listener) - } - } - } - - override suspend fun setSecureFlag(enable: Boolean) = withContext(ioDispatcher) { - megaApiGateway.setSecureFlag(enable) - } - - override suspend fun getUnverifiedOutgoingShares(): Int = 5 override suspend fun searchInShares( query: String, megaCancelToken: MegaCancelToken, @@ -448,4 +432,18 @@ internal class MegaNodeRepositoryImpl @Inject constructor( ) } } + + override suspend fun upgradeSecurity() = withContext(ioDispatcher) { + suspendCancellableCoroutine { continuation -> + val listener = continuation.getRequestListener { return@getRequestListener } + megaApiGateway.upgradeSecurity(listener) + continuation.invokeOnCancellation { + megaApiGateway.removeRequestListener(listener) + } + } + } + + override suspend fun setSecureFlag(enable: Boolean) = withContext(ioDispatcher) { + megaApiGateway.setSecureFlag(enable) + } } diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/entity/ShareData.kt b/domain/src/main/kotlin/mega/privacy/android/domain/entity/ShareData.kt index 8835b2ac224..245595ddb0f 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/entity/ShareData.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/entity/ShareData.kt @@ -8,6 +8,7 @@ package mega.privacy.android.domain.entity * @property access * @property timeStamp * @property isPending + * @param isVerified */ data class ShareData( val user: String?, @@ -15,4 +16,5 @@ data class ShareData( val access: Int, val timeStamp: Long, val isPending: Boolean, + val isVerified: Boolean, ) \ No newline at end of file From 59cc1fcdce78d20bb5b0a4a0eda3ddec6fd4d92e Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 2 Mar 2023 01:13:32 +1300 Subject: [PATCH 133/334] Cannot verify contact dialog displayed if user is not in contact list --- .../NodeOptionsBottomSheetDialogFragment.java | 35 ++++++- .../presentation/manager/ManagerViewModel.kt | 3 +- .../shares/outgoing/OutgoingSharesFragment.kt | 85 ++++++++++++---- .../outgoing/OutgoingSharesViewModel.kt | 7 ++ app/src/main/res/values/strings.xml | 98 ++++++++++--------- 5 files changed, 158 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index ef74637660e..0a68dd7c337 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -71,6 +71,7 @@ import androidx.lifecycle.Lifecycle; import androidx.lifecycle.ViewModelProvider; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.switchmaterial.SwitchMaterial; import com.jeremyliao.liveeventbus.LiveEventBus; @@ -1106,10 +1107,14 @@ public void onClick(View v) { requireActivity().startActivityForResult(version, REQUEST_CODE_DELETE_VERSIONS_HISTORY); break; case R.id.verify_user_option: - Intent authenticityCredentialsIntent = new Intent(getActivity(), AuthenticityCredentialsActivity.class); - authenticityCredentialsIntent.putExtra(Constants.IS_NODE_INCOMING, nC.nodeComesFromIncoming(node)); - authenticityCredentialsIntent.putExtra(Constants.EMAIL, user.getEmail()); - requireActivity().startActivity(authenticityCredentialsIntent); + ShareData shareData = outgoingSharesViewModel.getUnVerifiedOutgoingNodeShare(node.getHandle()); + if (shareData != null) { + if (!shareData.isVerified() && shareData.isPending()) { + showCanNotVerifyContact(shareData.getUser()); + } else { + openAuthenticityCredentials(shareData.getUser()); + } + } break; default: break; @@ -1264,4 +1269,26 @@ private void showShareFolderOptions() { } dismissAllowingStateLoss(); } + + /** + * Show cannot verify contact dialog + * @param email : Email of the user + */ + private void showCanNotVerifyContact(String email) { + new MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Mega_MaterialAlertDialog) + .setTitle(getString(R.string.shared_items_contact_not_in_contact_list_dialog_title)) + .setMessage(StringResourcesUtils.getString(R.string.shared_items_contact_not_in_contact_list_dialog_content, email)) + .setPositiveButton(getString(R.string.general_ok), (dialogInterface, i2) -> dialogInterface.dismiss()).show(); + } + + /** + * Open authenticityCredentials screen to verify user + * @param email : Email of the user + */ + private void openAuthenticityCredentials(String email) { + Intent authenticityCredentialsIntent = new Intent(getActivity(), AuthenticityCredentialsActivity.class); + authenticityCredentialsIntent.putExtra(Constants.IS_NODE_INCOMING, nC.nodeComesFromIncoming(node)); + authenticityCredentialsIntent.putExtra(Constants.EMAIL, email); + requireActivity().startActivity(authenticityCredentialsIntent); + } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index 140acaa38d7..103fbc0c0c7 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -178,7 +178,8 @@ class ManagerViewModel @Inject constructor( } viewModelScope.launch { - val outgoingShares = getUnverifiedOutgoingShares(getCloudSortOrder()).size + val outgoingShares = + getUnverifiedOutgoingShares(getCloudSortOrder()).filter { !it.isVerified }.size _state.update { it.copy(pendingActionsCount = _state.value.pendingActionsCount + outgoingShares) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 303b83bbf2f..bdb0e3f636b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -1,5 +1,6 @@ package mega.privacy.android.app.presentation.shares.outgoing +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.Menu @@ -12,11 +13,13 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import mega.privacy.android.app.R import mega.privacy.android.app.components.NewGridRecyclerView import mega.privacy.android.app.main.adapters.MegaNodeAdapter +import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.manager.model.Tab import mega.privacy.android.app.presentation.shares.MegaNodeBaseFragment @@ -27,6 +30,7 @@ import mega.privacy.android.app.utils.Constants.ORDER_OTHERS import mega.privacy.android.app.utils.MegaNodeUtil.areAllFileNodesAndNotTakenDown import mega.privacy.android.app.utils.MegaNodeUtil.areAllNotTakenDown import mega.privacy.android.app.utils.MegaNodeUtil.canMoveToRubbish +import mega.privacy.android.app.utils.StringResourcesUtils import mega.privacy.android.app.utils.Util import mega.privacy.android.domain.entity.SortOrder import nz.mega.sdk.MegaApiJava.INVALID_HANDLE @@ -91,27 +95,35 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { override fun itemClick(position: Int) { val actualPosition = position - 1 - - when { - // select mode - adapter?.isMultipleSelect == true -> { - adapter?.toggleSelection(position) - val selectedNodes = adapter?.selectedNodes - if ((selectedNodes?.size ?: 0) > 0) - updateActionModeTitle() + val clickedNodeHandle = adapter?.getItem(position)?.handle + viewModel.getUnVerifiedOutgoingNodeShare(clickedNodeHandle)?.let { + if (!it.isVerified && it.isPending) { + showCanNotVerifyContact(it.user) + } else { + openAuthenticityCredentials(it.user) } + } ?: run { + when { + // select mode + adapter?.isMultipleSelect == true -> { + adapter?.toggleSelection(position) + val selectedNodes = adapter?.selectedNodes + if ((selectedNodes?.size ?: 0) > 0) + updateActionModeTitle() + } - // click on a folder - state().nodes[actualPosition].isFolder -> - navigateToFolder(state().nodes[actualPosition]) - - // click on a file - else -> - openFile( - state().nodes[actualPosition], - Constants.OUTGOING_SHARES_ADAPTER, - actualPosition - ) + // click on a folder + state().nodes[actualPosition].isFolder -> + navigateToFolder(state().nodes[actualPosition]) + + // click on a file + else -> + openFile( + state().nodes[actualPosition], + Constants.OUTGOING_SHARES_ADAPTER, + actualPosition + ) + } } } @@ -380,4 +392,39 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { return true } } + + /** + * Show cannot verify contact dialog + * @param email : Email of the user + */ + private fun showCanNotVerifyContact(email: String?) { + MaterialAlertDialogBuilder( + requireContext(), + R.style.ThemeOverlay_Mega_MaterialAlertDialog + ).setTitle(getString(R.string.shared_items_contact_not_in_contact_list_dialog_title)) + .setMessage( + StringResourcesUtils.getString( + R.string.shared_items_contact_not_in_contact_list_dialog_content, + email + ) + ) + .setPositiveButton( + getString(R.string.general_ok) + ) { dialogInterface, _ -> dialogInterface.dismiss() } + .show() + } + + /** + * Open authenticityCredentials screen to verify user + * @param email : Email of the user + */ + private fun openAuthenticityCredentials(email: String?) { + Intent( + requireActivity(), + AuthenticityCredentialsActivity::class.java + ).apply { + putExtra(Constants.EMAIL, email) + requireActivity().startActivity(this) + } + } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 794e8b9daf9..a2a75f8d293 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -188,4 +188,11 @@ class OutgoingSharesViewModel @Inject constructor( ?.let { getNodeByHandle(it) == null } ?: true } + + /** + * Find if nodehandle is in unverifiedOutgoingShares list + */ + fun getUnVerifiedOutgoingNodeShare(handle: Long?) = + state.value.unverifiedOutgoingShares.find { node -> node.nodeHandle == handle } + } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 186f798e316..6a434811225 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,4 +1,4 @@ - + Pro Lite @@ -234,7 +234,7 @@ Links - Incoming Shares + Incoming shares Incoming shares with @@ -242,7 +242,7 @@ Empty Folder - CHOOSE ACCOUNT + Choose account Available Offline @@ -516,7 +516,7 @@ There is no payment method set for this plan currently. Please select one. - The link has been copied to the clipboard + Link copied to clipboard Please log out before creating the account @@ -677,7 +677,7 @@ Photos and videos - Passcode Lock + Passcode lock Change passcode @@ -1122,8 +1122,8 @@ %1$s changed the group chat name to “%2$s” %1$s left the group chat - - Copied to the clipboard + + Copied to clipboard Chat error @@ -1335,8 +1335,8 @@ - Link removed successfully. - Links removed successfully. + Link removed + Links removed Contacts attached @@ -1527,7 +1527,7 @@ New text file - File Name + File name Link name @@ -1596,7 +1596,7 @@ MEGA users who scan your QR code will be automatically added to your contact list. - Link copied to the clipboard + Link copied to clipboard QR code successfully reset @@ -1757,7 +1757,7 @@ Video quality - Video Quality + Video quality Camera uploads needs to access your photos and other media on your device. Please go to the settings page and grant permission. @@ -1880,9 +1880,9 @@ Rich URL Previews - Always Allow + Always allow - Not Now + Not now Never @@ -1941,7 +1941,7 @@ Invalid code - Lost your Authenticator device? + Lost your authenticator device? Login Verification @@ -2026,29 +2026,29 @@ This action cannot be completed as it would take you over your current storage limit. Would you like to upgrade your account? - Archived chats + Archived - Archived chats (%d) + Archived (%d) - Archive chat + Archive - Unarchive chat + Unarchive Archive Unarchive - %s chat was archived. + %s was archived - Error. %s chat was not archived. + Error. %s was not archived - %s chat was unarchived. + %s was unarchived - Error. %s chat was not able to be unarchived. + Error. %s can’t be unarchived Inactive chat - Archived chat + Archived Tap to join the call @@ -2085,7 +2085,7 @@ An error occurred while trying to delete all previous versions of your files, please try again later. - File Versioning + File versioning Enable or disable file versioning for your entire account.\nDisabling file versioning does not prevent your contacts from creating new versions in shared folders. @@ -2408,13 +2408,13 @@ Before you can generate a link for this chat, you need to set a description: - Chat link copied to the clipboard + Chat link copied to clipboard [A]From[/A] %s / [A]month[/A] * * Recurring subscription can be cancelled any time before the renewal date. - Call Started + Call started SSL key error @@ -2503,7 +2503,7 @@ This location will be opened using a third party maps provider outside the end-to-end encrypted MEGA platform. - Send this Location + Send this location The GPS is disabled @@ -2636,13 +2636,13 @@ Call on hold - Hold and Answer + Hold and answer Hold and Join - End and Answer + End and answer - End and Join + End and join File already downloaded. Copied to the selected path. @@ -3061,13 +3061,13 @@ Copy password - Key copied to the clipboard. + Key copied to clipboard - Password copied to the clipboard. - - Link and key successfully sent. - - Link and password successfully sent. + Password copied to clipboard + + Link and key sent + + Link and password sent Upgrade to Pro @@ -3121,7 +3121,7 @@ Cookie Policy - Cookie Settings + Cookie settings Your privacy @@ -3141,7 +3141,7 @@ Help us to understand how you use our services and provide us data that we can use to make improvements. Not accepting these Cookies will mean we will have less data available to us to help design improvements. - Always On + Always on Scan document @@ -3301,7 +3301,7 @@ %1$d connections - Recently Added + Recently added [B]No [/B][A]groups[/A] @@ -3314,13 +3314,13 @@ Copy all - Link successfully sent. - Links successfully sent. + Link sent + Links sent - Link copied to the clipboard. - Links copied to the clipboard. + Link copied to clipboard + Links copied to clipboard @@ -4188,9 +4188,9 @@ [A]%1$s[/A] from [B]%2$s to %3$s[/B] - Everyday effective [A]%1$s[/A] from [B]%2$s to %3$s[/B] + Every day effective [A]%1$s[/A] from [B]%2$s to %3$s[/B] - Everyday effective [A]%1$s until %2$s[/A] from [B]%3$s to %4$s[/B] + Every day effective [A]%1$s until %2$s[/A] from [B]%3$s to %4$s[/B] %1$s every week effective [A]%3$s until %4$s[/A] from [B]%5$s to %6$s[/B] @@ -4633,4 +4633,10 @@ Sat Sun + + Cannot add more + + You can add only up to 150 items at a time to an album. + Cannot verify contact + You can’t verify %s as they’re not in your contact list. Wait for them to accept your invitation first. \ No newline at end of file From ec8b9558df6478b927d90993ba0f89e8d7011ab7 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 2 Mar 2023 02:38:29 +1300 Subject: [PATCH 134/334] Decrypted Incoming folder name changed to actual name --- .../assets/featuretoggle/feature_flags.json | 2 +- .../assets/featuretoggle/feature_flags.json | 2 +- .../app/main/adapters/MegaNodeAdapter.java | 26 ++++++++++++++++--- .../NodeOptionsBottomSheetDialogFragment.java | 3 +++ .../recentactions/RecentActionsAdapter.kt | 12 ++++++++- .../shares/outgoing/OutgoingSharesFragment.kt | 12 +++++---- .../assets/featuretoggle/feature_flags.json | 2 +- build.gradle | 2 +- 8 files changed, 47 insertions(+), 14 deletions(-) diff --git a/app/src/debug/assets/featuretoggle/feature_flags.json b/app/src/debug/assets/featuretoggle/feature_flags.json index 25abad8e4c3..08678951a98 100644 --- a/app/src/debug/assets/featuretoggle/feature_flags.json +++ b/app/src/debug/assets/featuretoggle/feature_flags.json @@ -5,6 +5,6 @@ }, { "name": "SetSecureFlag", - "value": false + "value": true } ] \ No newline at end of file diff --git a/app/src/main/assets/featuretoggle/feature_flags.json b/app/src/main/assets/featuretoggle/feature_flags.json index 5f8aa914f37..376b278cc57 100644 --- a/app/src/main/assets/featuretoggle/feature_flags.json +++ b/app/src/main/assets/featuretoggle/feature_flags.json @@ -5,6 +5,6 @@ }, { "name": "SetSecureFlag", - "value": false + "value": true } ] \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 00b71400c09..e3b6f9a36e5 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -102,6 +102,7 @@ import mega.privacy.android.app.utils.ThumbnailUtils; import mega.privacy.android.data.database.DatabaseHandler; import mega.privacy.android.data.model.MegaContactDB; +import mega.privacy.android.domain.entity.ShareData; import mega.privacy.android.domain.entity.SortOrder; import nz.mega.sdk.MegaApiAndroid; import nz.mega.sdk.MegaNode; @@ -148,6 +149,8 @@ public class MegaNodeAdapter extends RecyclerView.Adapter unverifiedIncomingNodeHandles = new HashSet<>(); private final Set unverifiedOutgoingNodeHandles = new HashSet<>(); + private final List unverifiedOutGoingShareData = new ArrayList<>(); + public static class ViewHolderBrowser extends RecyclerView.ViewHolder { private ViewHolderBrowser(View v) { @@ -1092,7 +1095,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { boolean hasUnverifiedNodes = !unverifiedIncomingNodeHandles.isEmpty() && unverifiedIncomingNodeHandles.contains(node.getHandle()); if (hasUnverifiedNodes) { - showUnverifiedNodeUi(holder, true); + showUnverifiedNodeUi(holder, true, node); } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { @@ -1105,7 +1108,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { boolean hasUnverifiedNodes = !unverifiedOutgoingNodeHandles.isEmpty() && unverifiedOutgoingNodeHandles.contains(node.getHandle()); if (hasUnverifiedNodes) { - showUnverifiedNodeUi(holder, false); + showUnverifiedNodeUi(holder, false, node); } } } else { @@ -1537,6 +1540,11 @@ public void setUnverifiedOutgoingNodeHandles(List handles) { unverifiedOutgoingNodeHandles.addAll(handles); } + public void setUnverifiedOutgoingShareData(List shareDataList) { + unverifiedOutGoingShareData.clear(); + unverifiedOutGoingShareData.addAll(shareDataList); + } + /** * Function to show Unverified node UI items accordingly * @@ -1544,9 +1552,19 @@ public void setUnverifiedOutgoingNodeHandles(List handles) { * @param isIncomingNode boolean to indicate if the node is incoming so that * "Undecrypted folder" is displayed instead of node name */ - private void showUnverifiedNodeUi(ViewHolderBrowserList holder, Boolean isIncomingNode) { + private void showUnverifiedNodeUi(ViewHolderBrowserList holder, Boolean isIncomingNode, MegaNode node) { if (isIncomingNode) { - holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + if(node.isNodeKeyDecrypted()) { + holder.textViewFileName.setText(node.getName()); + } else { + holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + } + } else { + for (int i = 0; i < unverifiedOutGoingShareData.size(); i++) { + if(unverifiedOutGoingShareData.get(i).getNodeHandle() == (node.getHandle()) && unverifiedOutGoingShareData.get(i).isPending()) { + holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); + } + } } holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 0a68dd7c337..4b17e2bef25 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -99,6 +99,7 @@ import mega.privacy.android.app.presentation.search.SearchViewModel; import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel; import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel; +import mega.privacy.android.app.presentation.shares.outgoing.model.OutgoingSharesState; import mega.privacy.android.app.utils.AlertDialogUtil; import mega.privacy.android.app.utils.Constants; import mega.privacy.android.app.utils.MegaNodeUtil; @@ -1114,6 +1115,8 @@ public void onClick(View v) { } else { openAuthenticityCredentials(shareData.getUser()); } + } else if(!node.isNodeKeyDecrypted()) { + openAuthenticityCredentials(user.getEmail()); } break; default: diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index b6f5288338c..800c0bc95a9 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -167,7 +167,17 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter { @@ -245,6 +246,7 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) + adapter?.setUnverifiedOutgoingShareData(it.unverifiedOutgoingShares) adapter?.setUnverifiedOutgoingNodeHandles(it.unVerifiedOutgoingNodeHandles) updateNodes(it.nodes) } diff --git a/app/src/qa/assets/featuretoggle/feature_flags.json b/app/src/qa/assets/featuretoggle/feature_flags.json index 25abad8e4c3..08678951a98 100644 --- a/app/src/qa/assets/featuretoggle/feature_flags.json +++ b/app/src/qa/assets/featuretoggle/feature_flags.json @@ -5,6 +5,6 @@ }, { "name": "SetSecureFlag", - "value": false + "value": true } ] \ No newline at end of file diff --git a/build.gradle b/build.gradle index 709c8481e7d..86cabdf7e01 100644 --- a/build.gradle +++ b/build.gradle @@ -69,7 +69,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230222.010407-rel' + megaSdkVersion = '20230228.235733-rel' // App dependencies accompanistLayoutVersion = '0.24.13-rc' From cc8918350ea3246609633310890996f03086c98e Mon Sep 17 00:00:00 2001 From: Yenel Date: Wed, 1 Mar 2023 09:42:51 +0100 Subject: [PATCH 135/334] T4575001 User should be able to upload the filtered result from chat --- .../privacy/android/app/main/IncomingSharesExplorerFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/IncomingSharesExplorerFragment.kt b/app/src/main/java/mega/privacy/android/app/main/IncomingSharesExplorerFragment.kt index 0bd5bc60f24..1460fa159b5 100644 --- a/app/src/main/java/mega/privacy/android/app/main/IncomingSharesExplorerFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/main/IncomingSharesExplorerFragment.kt @@ -749,7 +749,7 @@ class IncomingSharesExplorerFragment : RotatableFragment(), CheckScrollInterface * @param searchString search strings */ fun search(searchString: String?) { - if (searchString == null && !shouldResetNodes) { + if (searchString == null || !shouldResetNodes) { return } searchCancelToken = initNewSearch() From 8c46ad0c06803139b1d9d89606efd176b594ddee Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Fri, 3 Mar 2023 23:26:49 +1300 Subject: [PATCH 136/334] AND-15249 : Mandatory fingerprint authentication --- .../privacy/android/app/main/ManagerActivity.java | 2 ++ .../app/presentation/manager/ManagerViewModel.kt | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index c07c141c794..f0c5b4f2246 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -2455,6 +2455,8 @@ private void collectFlows() { dialog.setArguments(bundle); dialog.show(getSupportFragmentManager(), SecurityUpgradeDialogFragment.TAG); + viewModel.setShouldAlertUserAboutSecurityUpgrade(false); + return Unit.INSTANCE; }); } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index 103fbc0c0c7..7b5679d1771 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -439,11 +439,20 @@ class ManagerViewModel @Inject constructor( viewModelScope.launch { updateGlobalEvents.collect { megaEvent -> if (megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { - _state.update { - it.copy(shouldAlertUserAboutSecurityUpgrade = true) - } + setShouldAlertUserAboutSecurityUpgrade(true) } } } } + + /** + * Set the security upgrade alert state + * + * @param shouldShow true if the security upgrade alert needs to be shown + */ + fun setShouldAlertUserAboutSecurityUpgrade(shouldShow: Boolean) { + _state.update { + it.copy(shouldAlertUserAboutSecurityUpgrade = shouldShow) + } + } } From 221bd8ffedb788a2fe01c4c1c417f8bd9e97f085 Mon Sep 17 00:00:00 2001 From: Yenel Date: Fri, 3 Mar 2023 13:28:32 +0100 Subject: [PATCH 137/334] Merge branch 'master' into release/v7.5 --- sdk/src/main/jni/mega/sdk | 2 +- sdk/src/main/jni/megachat/sdk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index 5c150ba80c9..0bb1b66b527 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit 5c150ba80c9fe7af85778aef88dfe5637d2c17f7 +Subproject commit 0bb1b66b527093403a7435a12df2c1107c509c64 diff --git a/sdk/src/main/jni/megachat/sdk b/sdk/src/main/jni/megachat/sdk index 7cf3419b5d0..6f8ad72568d 160000 --- a/sdk/src/main/jni/megachat/sdk +++ b/sdk/src/main/jni/megachat/sdk @@ -1 +1 @@ -Subproject commit 7cf3419b5d0e43b0566fd38e4ad3cc5b1f60d3c6 +Subproject commit 6f8ad72568da4ea062b05030d663f54d08170724 From d9496baed4f0e6059f36a3cb3882fb7140caf542 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 23 Dec 2022 12:14:39 +0530 Subject: [PATCH 138/334] Functions for pending actions, openShareDialog, upgradeSecurity are added in MegaApi & implementations added in MegaApiFacade --- .../android/data/facade/MegaApiFacade.kt | 16 +++++++++ .../data/gateway/api/MegaApiGateway.kt | 35 +++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index 30cf8505666..c42fd5885f7 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -32,6 +32,7 @@ import nz.mega.sdk.MegaSetElement import nz.mega.sdk.MegaSetElementList import nz.mega.sdk.MegaSetList import nz.mega.sdk.MegaShare +import nz.mega.sdk.MegaShareList import nz.mega.sdk.MegaTransfer import nz.mega.sdk.MegaTransferListenerInterface import nz.mega.sdk.MegaUser @@ -871,4 +872,19 @@ internal class MegaApiFacade @Inject constructor( ) = megaApi.getPublicNode(nodeFileLink, listener) override suspend fun cancelTransfers(direction: Int) = megaApi.cancelTransfers(direction) + + override suspend fun getUnverifiedIncomingShares(order: Int): MegaShareList = + megaApi.getUnverifiedIncomingShares(order) + + override suspend fun getUnverifiedOutgoingShares(order: Int): MegaShareList = + megaApi.getUnverifiedIncomingShares(order) + + override fun openShareDialog( + megaNode: MegaNode, + listener: MegaRequestListenerInterface, + ) = megaApi.openShareDialog(megaNode, listener) + + override fun upgradeSecurity(listener: MegaRequestListenerInterface) = + megaApi.upgradeSecurity(listener) + } diff --git a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt index 9937881b224..e89672f0626 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt @@ -15,6 +15,7 @@ import nz.mega.sdk.MegaSet import nz.mega.sdk.MegaSetElementList import nz.mega.sdk.MegaSetList import nz.mega.sdk.MegaShare +import nz.mega.sdk.MegaShareList import nz.mega.sdk.MegaTransfer import nz.mega.sdk.MegaTransferListenerInterface import nz.mega.sdk.MegaUser @@ -1675,4 +1676,38 @@ interface MegaApiGateway { * - MegaTransfer::TYPE_UPLOAD = 1 */ suspend fun cancelTransfers(direction: Int) + + /** + * Function to get unverified incoming shares from [MegaApi] + * + * @param order : Sort order + * @return List of [MegaShare] + */ + suspend fun getUnverifiedIncomingShares(order: Int): MegaShareList + + /** + * Function to get unverified outgoing shares from [MegaApi] + * + * @param order : Sort order + * @return List of [MegaShare] + */ + suspend fun getUnverifiedOutgoingShares(order: Int): MegaShareList + + /** + * Creates a new share key for the node if there is no share key already created. + * + * @param megaNode : [MegaNode] object which needs to be shared + * @param listener : Listener to track this request + */ + fun openShareDialog( + megaNode: MegaNode, + listener: MegaRequestListenerInterface, + ) + + /** + * Update cryptographic security + * + * @param listener : Listener to track this request + */ + fun upgradeSecurity(listener: MegaRequestListenerInterface) } From 85d8ebebf2c9c037fa8c290e9e1da571106a62b2 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 26 Dec 2022 18:53:03 +0530 Subject: [PATCH 139/334] Return type changed to List --- .../java/mega/privacy/android/data/facade/MegaApiFacade.kt | 6 +++--- .../mega/privacy/android/data/gateway/api/MegaApiGateway.kt | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index c42fd5885f7..75d157ff40d 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -14,6 +14,7 @@ import mega.privacy.android.data.listener.OptionalMegaTransferListenerInterface import mega.privacy.android.data.model.GlobalTransfer import mega.privacy.android.data.model.GlobalUpdate import mega.privacy.android.data.qualifier.MegaApi +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.qualifier.ApplicationScope import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaApiJava @@ -32,7 +33,6 @@ import nz.mega.sdk.MegaSetElement import nz.mega.sdk.MegaSetElementList import nz.mega.sdk.MegaSetList import nz.mega.sdk.MegaShare -import nz.mega.sdk.MegaShareList import nz.mega.sdk.MegaTransfer import nz.mega.sdk.MegaTransferListenerInterface import nz.mega.sdk.MegaUser @@ -873,10 +873,10 @@ internal class MegaApiFacade @Inject constructor( override suspend fun cancelTransfers(direction: Int) = megaApi.cancelTransfers(direction) - override suspend fun getUnverifiedIncomingShares(order: Int): MegaShareList = + override suspend fun getUnverifiedIncomingShares(order: Int): List = megaApi.getUnverifiedIncomingShares(order) - override suspend fun getUnverifiedOutgoingShares(order: Int): MegaShareList = + override suspend fun getUnverifiedOutgoingShares(order: Int): List = megaApi.getUnverifiedIncomingShares(order) override fun openShareDialog( diff --git a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt index e89672f0626..0acef30291b 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt @@ -3,6 +3,7 @@ package mega.privacy.android.data.gateway.api import kotlinx.coroutines.flow.Flow import mega.privacy.android.data.model.GlobalTransfer import mega.privacy.android.data.model.GlobalUpdate +import mega.privacy.android.domain.entity.ShareData import nz.mega.sdk.MegaCancelToken import nz.mega.sdk.MegaContactRequest import nz.mega.sdk.MegaError @@ -15,7 +16,6 @@ import nz.mega.sdk.MegaSet import nz.mega.sdk.MegaSetElementList import nz.mega.sdk.MegaSetList import nz.mega.sdk.MegaShare -import nz.mega.sdk.MegaShareList import nz.mega.sdk.MegaTransfer import nz.mega.sdk.MegaTransferListenerInterface import nz.mega.sdk.MegaUser @@ -1683,7 +1683,7 @@ interface MegaApiGateway { * @param order : Sort order * @return List of [MegaShare] */ - suspend fun getUnverifiedIncomingShares(order: Int): MegaShareList + suspend fun getUnverifiedIncomingShares(order: Int): List /** * Function to get unverified outgoing shares from [MegaApi] @@ -1691,7 +1691,7 @@ interface MegaApiGateway { * @param order : Sort order * @return List of [MegaShare] */ - suspend fun getUnverifiedOutgoingShares(order: Int): MegaShareList + suspend fun getUnverifiedOutgoingShares(order: Int): List /** * Creates a new share key for the node if there is no share key already created. From 88ac714dcd1cb96124d23797368b1687467d9f37 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 27 Dec 2022 14:54:15 +0530 Subject: [PATCH 140/334] Security upgrade dialog strings added --- app/src/main/res/values/strings.xml | 110 +++++++--------------------- 1 file changed, 27 insertions(+), 83 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 59c7ee139a2..87336726d87 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -790,7 +790,7 @@ Save - Recovery key copied to clipboard. Save it to a safe place where you can easily access later. + The Recovery key has been successfully copied Change @@ -1506,6 +1506,8 @@ You have received %1$s storage space for verifying your phone number. You have received %1$s storage space as your free registration bonus. + + Bonus expires in %1$d days Share folder @@ -3581,6 +3583,22 @@ Payment methods Proceed + + Pro Lite monthly + + Pro Lite yearly + + Pro I monthly + + Pro I yearly + + Pro II monthly + + Pro II yearly + + Pro III monthly + + Pro III yearly Remove as host @@ -3915,6 +3933,8 @@ Start chatting now Chat securely and privately, with anyone and on any device, knowing that no one can read your chats, not even MEGA. + + Archive meeting Start meeting now @@ -4007,6 +4027,8 @@ You’ve already added all your contacts to this chat. If you want to add more participants, first invite them to your contact list. Saved image to your device gallery + + [A]Renewal date:[/A] [B]%s[/B] Enter album name @@ -4061,7 +4083,7 @@ Recurring meeting - %s Weekly + %s weekly Media discovery view @@ -4116,85 +4138,7 @@ Bonus expires in %1$d day Bonus expires in %1$d days - - Description - - Remove from album? - - To enable camera uploads, grant MEGA access to your photos and other media on your device. - - Grant access - - Don’t grant - - - Removed %d item from “%s” - Removed %d items from “%s” - - - One-off meeting - - Resume video? - - %1$s will resume from %2$s - - Resume - - Restart - - [A]%s [/A][B]invited you to a meeting scheduled for:[/B] - - [A]%s cancelled[/A][B] the meeting scheduled for:[/B] - - [A]%s updated the meeting name[/A][B] from “%s” to [/B]“[A]%s[/A]“ - - [A]%s updated[/A][B] the meeting date[/B] - - [A]%s updated[/A][B] the meeting time[/B] - - [A]%s updated[/A][B] the meeting description[/B] - - [A]%s updated[/A][B] the meeting details scheduled for:[/B] - - Access denied - - You denied MEGA access to your device’s storage and media files. If you’d like to continue sharing, allow MEGA permission. - - Allow permission - - Don’t allow - - Occurrences - - Occurs daily - - Occurs weekly - - Occurs monthly - - See more occurrences - - %s Monthly - - %s Daily - - [A]%s [/A][B]invited you to a recurring meeting scheduled for:[/B] - - [A]%s updated[/A][B] the recurring meeting description[/B] - - [A]%s updated[/A][B] the recurring meeting details scheduled for:[/B] - - [A]%s updated[/A][B] an occurrence to:[/B] - - [A]%s updated[/A][B] the recurring meeting time[/B] - - [A]%s updated[/A][B] the recurring meeting frequency[/B] - - [A]%s cancelled[/A][B] the meeting and all its occurrences[/B] - - [A]%s cancelled[/A][B] the occurrence scheduled for:[/B] - - This link hasn’t been generated with the account you’re currently logged into. Log in to the account related to this link to verify your email address. - - Unable to update email address + Security upgrade + Your account’s security is now being upgraded. This will happen only once. If you have seen this message for this account before, press Cancel. + You are currently sharing the following folders: %s \ No newline at end of file From 300412f6c161afe7930c0bde419e09a84d760576 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 08:50:09 +0530 Subject: [PATCH 141/334] Function return types & parameters updated. openShareDialog & upgradeSecurity changed to suspend --- .../mega/privacy/android/data/facade/MegaApiFacade.kt | 8 ++++---- .../privacy/android/data/gateway/api/MegaApiGateway.kt | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index 75d157ff40d..203c7772cb8 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -873,18 +873,18 @@ internal class MegaApiFacade @Inject constructor( override suspend fun cancelTransfers(direction: Int) = megaApi.cancelTransfers(direction) - override suspend fun getUnverifiedIncomingShares(order: Int): List = + override suspend fun getUnverifiedIncomingShares(order: Int): List = megaApi.getUnverifiedIncomingShares(order) - override suspend fun getUnverifiedOutgoingShares(order: Int): List = + override suspend fun getUnverifiedOutgoingShares(order: Int): List = megaApi.getUnverifiedIncomingShares(order) - override fun openShareDialog( + override suspend fun openShareDialog( megaNode: MegaNode, listener: MegaRequestListenerInterface, ) = megaApi.openShareDialog(megaNode, listener) - override fun upgradeSecurity(listener: MegaRequestListenerInterface) = + override suspend fun upgradeSecurity(listener: MegaRequestListenerInterface) = megaApi.upgradeSecurity(listener) } diff --git a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt index 0acef30291b..0e991d7fb54 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt @@ -1683,7 +1683,7 @@ interface MegaApiGateway { * @param order : Sort order * @return List of [MegaShare] */ - suspend fun getUnverifiedIncomingShares(order: Int): List + suspend fun getUnverifiedIncomingShares(order: Int): List /** * Function to get unverified outgoing shares from [MegaApi] @@ -1691,7 +1691,7 @@ interface MegaApiGateway { * @param order : Sort order * @return List of [MegaShare] */ - suspend fun getUnverifiedOutgoingShares(order: Int): List + suspend fun getUnverifiedOutgoingShares(order: Int): List /** * Creates a new share key for the node if there is no share key already created. @@ -1699,7 +1699,7 @@ interface MegaApiGateway { * @param megaNode : [MegaNode] object which needs to be shared * @param listener : Listener to track this request */ - fun openShareDialog( + suspend fun openShareDialog( megaNode: MegaNode, listener: MegaRequestListenerInterface, ) @@ -1709,5 +1709,5 @@ interface MegaApiGateway { * * @param listener : Listener to track this request */ - fun upgradeSecurity(listener: MegaRequestListenerInterface) + suspend fun upgradeSecurity(listener: MegaRequestListenerInterface) } From 4c052daed70c24093ba2838558954987e684662e Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 09:02:35 +0530 Subject: [PATCH 142/334] FillMaxWidth & FillMaxHeight replaced with FillMaxSize --- .../SecurityUpgradeDialogView.kt | 127 ++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt new file mode 100644 index 00000000000..06956ba3a26 --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt @@ -0,0 +1,127 @@ +package mega.privacy.android.app.presentation.fingerprintauth + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import mega.privacy.android.app.R +import mega.privacy.android.presentation.theme.body2 +import mega.privacy.android.presentation.theme.jade_300 +import mega.privacy.android.presentation.theme.subtitle1 + +/** + * Security upgrade dialog body + * + * @param folderName : Name of the folder for which security settings will upgrade + * @param onOkClick : Ok button click listener + * @param onCancelClick : Cancel button click listener + */ +@Composable +fun SecurityUpgradeDialogView( + folderName: String, + onOkClick: () -> Unit, + onCancelClick: () -> Unit, +) { + Surface(modifier = Modifier + .padding(10.dp) + .fillMaxSize(), + shape = RoundedCornerShape(8.dp), + color = if (MaterialTheme.colors.isLight) { + Color.White + } else { + Color.Transparent + }, + content = { + Column(modifier = Modifier.padding(start = 16.dp, end = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + content = { + Image(modifier = Modifier + .height(140.dp) + .width(114.dp) + .testTag("HeaderImage"), + imageVector = ImageVector.vectorResource(id = R.drawable.ic_contact_verification_required), + contentDescription = "Empty") + + Text(text = stringResource(id = R.string.shared_items_security_upgrade_dialog_title), + style = subtitle1, + color = if (MaterialTheme.colors.isLight) { + Color.Black + } else { + Color.White + }) + + Spacer(Modifier.height(16.dp)) + + Text(text = stringResource(id = R.string.shared_items_security_upgrade_dialog_content), + style = body2.copy(textAlign = TextAlign.Center), + color = if (MaterialTheme.colors.isLight) { + Color.Black + } else { + Color.White + }) + + Spacer(Modifier.height(20.dp)) + + Text(modifier = Modifier.testTag("SharedNodeInfo"), + text = stringResource(id = R.string.shared_items_security_upgrade_dialog_node_sharing_info, + folderName), + style = body2.copy(textAlign = TextAlign.Center), + color = if (MaterialTheme.colors.isLight) { + Color.Black + } else { + Color.White + }) + + Spacer(Modifier.height(20.dp)) + + Button(modifier = Modifier + .height(45.dp) + .fillMaxWidth() + .padding(start = 25.dp, end = 25.dp), + shape = RoundedCornerShape(8.dp), + content = { + Text(text = stringResource(id = R.string.general_ok), + color = Color.White) + }, + colors = ButtonDefaults.buttonColors(backgroundColor = jade_300), + onClick = onOkClick) + + Spacer(Modifier.height(10.dp)) + + Button(modifier = Modifier + .height(45.dp) + .fillMaxWidth() + .padding(start = 25.dp, end = 25.dp), + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors(backgroundColor = if (MaterialTheme.colors.isLight) Color.White else Color.DarkGray), + onClick = onCancelClick) { + Text(text = stringResource(id = R.string.button_cancel), + color = if (MaterialTheme.colors.isLight) { + Color.Black + } else { + jade_300 + }) + } + }) + }) +} \ No newline at end of file From a21e67ae296f8df34ad74a508f7700495b534a1d Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 09:50:08 +0530 Subject: [PATCH 143/334] Suspend removed & imports optimised --- .../java/mega/privacy/android/data/facade/MegaApiFacade.kt | 5 ++--- .../mega/privacy/android/data/gateway/api/MegaApiGateway.kt | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index 203c7772cb8..d25712811dd 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -14,7 +14,6 @@ import mega.privacy.android.data.listener.OptionalMegaTransferListenerInterface import mega.privacy.android.data.model.GlobalTransfer import mega.privacy.android.data.model.GlobalUpdate import mega.privacy.android.data.qualifier.MegaApi -import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.qualifier.ApplicationScope import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaApiJava @@ -879,12 +878,12 @@ internal class MegaApiFacade @Inject constructor( override suspend fun getUnverifiedOutgoingShares(order: Int): List = megaApi.getUnverifiedIncomingShares(order) - override suspend fun openShareDialog( + override fun openShareDialog( megaNode: MegaNode, listener: MegaRequestListenerInterface, ) = megaApi.openShareDialog(megaNode, listener) - override suspend fun upgradeSecurity(listener: MegaRequestListenerInterface) = + override fun upgradeSecurity(listener: MegaRequestListenerInterface) = megaApi.upgradeSecurity(listener) } diff --git a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt index 0e991d7fb54..bfc8144b431 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt @@ -3,7 +3,6 @@ package mega.privacy.android.data.gateway.api import kotlinx.coroutines.flow.Flow import mega.privacy.android.data.model.GlobalTransfer import mega.privacy.android.data.model.GlobalUpdate -import mega.privacy.android.domain.entity.ShareData import nz.mega.sdk.MegaCancelToken import nz.mega.sdk.MegaContactRequest import nz.mega.sdk.MegaError @@ -1699,7 +1698,7 @@ interface MegaApiGateway { * @param megaNode : [MegaNode] object which needs to be shared * @param listener : Listener to track this request */ - suspend fun openShareDialog( + fun openShareDialog( megaNode: MegaNode, listener: MegaRequestListenerInterface, ) @@ -1709,5 +1708,5 @@ interface MegaApiGateway { * * @param listener : Listener to track this request */ - suspend fun upgradeSecurity(listener: MegaRequestListenerInterface) + fun upgradeSecurity(listener: MegaRequestListenerInterface) } From c4d2dddacd7b11b36cc312990badbdf7fcdd3780 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 11:03:20 +0530 Subject: [PATCH 144/334] openShareDialog , upgradeSecurity changed to suspend functions. --- .../data/repository/DefaultFilesRepository.kt | 0 .../data/repository/MegaNodeRepository.kt | 21 +++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 data/src/main/java/mega/privacy/android/data/repository/DefaultFilesRepository.kt diff --git a/data/src/main/java/mega/privacy/android/data/repository/DefaultFilesRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/DefaultFilesRepository.kt new file mode 100644 index 00000000000..e69de29bb2d diff --git a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt index 45d2b31e06b..93e10042a6d 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt @@ -1,6 +1,7 @@ package mega.privacy.android.data.repository import mega.privacy.android.domain.entity.FolderVersionInfo +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.exception.MegaException @@ -236,14 +237,26 @@ interface MegaNodeRepository { /** * Provides Unverified incoming shares count from SDK * - * @return Integer count + * @return List of [ShareData] */ - suspend fun getUnVerifiedInComingShares(): Int + suspend fun getUnVerifiedInComingShares(order: SortOrder): List /** * Provides Unverified outgoing shares count from SDK * - * @return Integer count + * @return List of [ShareData] */ - suspend fun getUnverifiedOutgoingShares(): Int + suspend fun getUnverifiedOutgoingShares(order: SortOrder): List + + /** + * Creates a new share key for the node if there is no share key already created. + * + * @param megaNode : [MegaNode] object which needs to be shared + */ + suspend fun openShareDialog(megaNode: MegaNode) + + /** + * Update cryptographic security + */ + suspend fun upgradeSecurity() } \ No newline at end of file From f11e9d89bc86d96253a7343b919f358c3f248273 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 11:15:39 +0530 Subject: [PATCH 145/334] Return type & input changed --- .../android/domain/usecase/GetUnverifiedIncomingShares.kt | 5 ++++- .../android/domain/usecase/GetUnverifiedOutgoingShares.kt | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedIncomingShares.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedIncomingShares.kt index acb7fb775f6..da43e7da4fb 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedIncomingShares.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedIncomingShares.kt @@ -1,5 +1,8 @@ package mega.privacy.android.domain.usecase +import mega.privacy.android.domain.entity.ShareData +import mega.privacy.android.domain.entity.SortOrder + /** * GetUnverifiedIncomingShares Use case */ @@ -8,5 +11,5 @@ fun interface GetUnverifiedIncomingShares { /** * @return Flow of unverified incoming shares */ - suspend operator fun invoke(): Int + suspend operator fun invoke(order: SortOrder): List } \ No newline at end of file diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedOutgoingShares.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedOutgoingShares.kt index db0354ecf47..2482b46ca62 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedOutgoingShares.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/GetUnverifiedOutgoingShares.kt @@ -1,5 +1,8 @@ package mega.privacy.android.domain.usecase +import mega.privacy.android.domain.entity.ShareData +import mega.privacy.android.domain.entity.SortOrder + /** * GetUnverifiedOutgoingShares Use case */ @@ -8,5 +11,5 @@ fun interface GetUnverifiedOutgoingShares { /** * @return Flow of unverified outgoing shares */ - suspend operator fun invoke(): Int + suspend operator fun invoke(order: SortOrder): List } \ No newline at end of file From b0641e81e10bda4ae93e902f927f6c3ed0f1434d Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 11:37:59 +0530 Subject: [PATCH 146/334] Viewmodel modified to solve compile issue --- .../presentation/shares/incoming/IncomingSharesViewModel.kt | 2 +- .../presentation/shares/incoming/model/IncomingSharesState.kt | 3 ++- .../presentation/shares/outgoing/OutgoingSharesViewModel.kt | 2 +- .../presentation/shares/outgoing/model/OutgoingSharesState.kt | 3 ++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index e57f1cefb4a..50b899df385 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -74,7 +74,7 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() _state.update { - it.copy(unVerifiedInComingShares = getUnverifiedInComingShares()) + it.copy(unVerifiedInComingShares = getUnverifiedInComingShares(_state.value.sortOrder)) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index b59cb756f5b..38caa5d6334 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -1,5 +1,6 @@ package mega.privacy.android.app.presentation.shares.incoming.model +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import nz.mega.sdk.MegaNode @@ -24,7 +25,7 @@ data class IncomingSharesState( val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val unVerifiedInComingShares: Int = 0, + val unVerifiedInComingShares: List = emptyList(), val isMandatoryFingerprintVerificationNeeded: Boolean = false, ) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index c07bf003304..a301d30e2fe 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -59,7 +59,7 @@ class OutgoingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() _state.update { - it.copy(unVerifiedOutGoingShares = getUnverifiedOutgoingShares()) + it.copy(unVerifiedOutGoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder)) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index d09e2fb7849..69f31bfebd8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -1,5 +1,6 @@ package mega.privacy.android.app.presentation.shares.outgoing.model +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import nz.mega.sdk.MegaNode @@ -24,7 +25,7 @@ data class OutgoingSharesState( val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val unVerifiedOutGoingShares: Int = 0, + val unVerifiedOutGoingShares: List = emptyList(), val isMandatoryFingerprintVerificationNeeded: Boolean = false, ) { From 72ca8e8a4c85f7b314b41bd84d82f39440d5e16a Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 12:33:39 +0530 Subject: [PATCH 147/334] Failing unit test modified to get success --- .../mega/privacy/android/app/di/TestGetNodeModule.kt | 4 ++-- .../shares/incoming/IncomingSharesViewModelTest.kt | 9 ++++++--- .../shares/outgoing/OutgoingSharesViewModelTest.kt | 6 ++++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt index 4d6a6d6281f..26fe7ed5176 100644 --- a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt +++ b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt @@ -40,11 +40,11 @@ object TestGetNodeModule { @Provides fun provideGetUnVerifiedInComingShares() = mock() { - onBlocking { invoke() }.thenReturn(3) + onBlocking { invoke(any()) }.thenReturn(emptyList()) } @Provides fun provideGetUnverifiedOutGoingShares() = mock() { - onBlocking { invoke() }.thenReturn(3) + onBlocking { invoke(any()) }.thenReturn(emptyList()) } } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index dbc8cb26374..2b7a7a4b989 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -15,6 +15,7 @@ import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeChanges @@ -94,7 +95,7 @@ class IncomingSharesViewModelTest { assertThat(initial.isInvalidHandle).isEqualTo(true) assertThat(initial.incomingParentHandle).isEqualTo(null) assertThat(initial.sortOrder).isEqualTo(SortOrder.ORDER_NONE) - assertThat(initial.unVerifiedInComingShares).isEqualTo(0) + assertThat(initial.unVerifiedInComingShares).isEmpty() } } @@ -519,10 +520,12 @@ class IncomingSharesViewModelTest { @Test fun `test that unverified incoming shares are returned`() = runTest { - whenever(getUnverifiedInComingShares()).thenReturn(3) + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + whenever(getUnverifiedInComingShares(underTest.state.value.sortOrder)).thenReturn(listOf( + shareData)) initViewModel() underTest.state.test { - assertThat(awaitItem().unVerifiedInComingShares).isEqualTo(3) + assertThat(awaitItem().unVerifiedInComingShares).isNotEmpty() } } } \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index 26f9c3c2848..9acc4133d7c 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -14,6 +14,7 @@ import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeId @@ -60,7 +61,8 @@ class OutgoingSharesViewModelTest { } private val getUnverifiedOutgoingShares = mock() { - onBlocking { invoke() }.thenReturn(5) + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } @Before @@ -446,7 +448,7 @@ class OutgoingSharesViewModelTest { fun `test that unverified incoming shares are returned`() = runTest { initViewModel() underTest.state.test { - assertThat(awaitItem().unVerifiedOutGoingShares).isEqualTo(5) + assertThat(awaitItem().unVerifiedOutGoingShares).isNotEmpty() } } } From 74f6f9123aceec837ddce3334652d3927ff2fcfc Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 15:26:04 +0530 Subject: [PATCH 148/334] Pending actions count badge added on Bottom Sheet bar --- .../android/app/main/ManagerActivity.java | 7 +- .../presentation/manager/ManagerViewModel.kt | 33 +++++++++ .../manager/model/ManagerState.kt | 2 + .../manager/ManagerViewModelTest.kt | 70 +++++++++++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index a63e04d5c70..9ed0603efc8 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -10270,8 +10270,11 @@ public void setChatBadge() { public void setPendingActionsBadge() { if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded()) { sharedItemsView.addView(pendingActionsBadge); - TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.chat_badge_text); - tvPendingActionsCount.setText("5"); + ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { + TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.chat_badge_text); + tvPendingActionsCount.setText(managerState.getPendingActionsCount()); + return Unit.INSTANCE; + }); } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index b7412bc114a..6326ca2494b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import mega.privacy.android.app.domain.usecase.GetInboxNode import mega.privacy.android.app.domain.usecase.GetPrimarySyncHandle +import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates @@ -32,6 +33,7 @@ import mega.privacy.android.app.presentation.manager.model.TransfersTab import mega.privacy.android.app.utils.livedata.SingleLiveEvent import mega.privacy.android.data.model.GlobalUpdate import mega.privacy.android.domain.entity.Product +import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.StorageState import mega.privacy.android.domain.entity.billing.MegaPurchase import mega.privacy.android.domain.entity.contacts.ContactRequest @@ -45,6 +47,8 @@ import mega.privacy.android.domain.usecase.GetExtendedAccountDetail import mega.privacy.android.domain.usecase.GetFullAccountInfo import mega.privacy.android.domain.usecase.GetNumUnreadUserAlerts import mega.privacy.android.domain.usecase.GetPricing +import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares +import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares import mega.privacy.android.domain.usecase.HasInboxChildren import mega.privacy.android.domain.usecase.MonitorConnectivity import mega.privacy.android.domain.usecase.MonitorContactRequestUpdates @@ -65,6 +69,7 @@ import javax.inject.Inject * * @param monitorNodeUpdates Monitor global node updates * @param monitorGlobalUpdates Monitor global updates + * @param getRubbishBinChildrenNode Fetch the rubbish bin nodes * @param monitorContactRequestUpdates * @param getInboxNode * @param getNumUnreadUserAlerts @@ -81,6 +86,7 @@ import javax.inject.Inject class ManagerViewModel @Inject constructor( monitorNodeUpdates: MonitorNodeUpdates, private val monitorGlobalUpdates: MonitorGlobalUpdates, + private val getRubbishBinChildrenNode: GetRubbishBinChildrenNode, monitorContactRequestUpdates: MonitorContactRequestUpdates, private val getInboxNode: GetInboxNode, private val getNumUnreadUserAlerts: GetNumUnreadUserAlerts, @@ -101,6 +107,8 @@ class ManagerViewModel @Inject constructor( private val getPricing: GetPricing, private val getFullAccountInfo: GetFullAccountInfo, private val getActiveSubscription: GetActiveSubscription, + private val getUnverifiedInComingShares: GetUnverifiedIncomingShares, + private val getUnverifiedOutgoingShares: GetUnverifiedOutgoingShares, ) : ViewModel() { /** @@ -139,6 +147,7 @@ class ManagerViewModel @Inject constructor( ) init { + viewModelScope.launch { monitorNodeUpdates().collect { val nodeList = it.changes.keys.toList() @@ -154,6 +163,14 @@ class ManagerViewModel @Inject constructor( _state.update(it) } } + + viewModelScope.launch { + val incomingShares = getUnverifiedInComingShares(SortOrder.ORDER_DEFAULT_ASC).size + val outgoingShares = getUnverifiedOutgoingShares(SortOrder.ORDER_DEFAULT_ASC).size + _state.update { + it.copy(pendingActionsCount = incomingShares + outgoingShares) + } + } } /** @@ -163,6 +180,12 @@ class ManagerViewModel @Inject constructor( private val _updates = monitorGlobalUpdates() .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) + /** + * Monitor global node updates + */ + private val _updateNodes = monitorNodeUpdates() + .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) + /** * Monitor contact requests */ @@ -208,6 +231,16 @@ class ManagerViewModel @Inject constructor( .map { Event(it) } .asLiveData() + /** + * Update Rubbish Nodes when a node update callback happens + */ + val updateRubbishBinNodes: LiveData>> = + _updateNodes + .also { Timber.d("onRubbishNodesUpdate") } + .mapNotNull { getRubbishBinChildrenNode(_state.value.rubbishBinParentHandle) } + .map { Event(it) } + .asLiveData() + /** * On my avatar file changed */ diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index 7b16ffcd029..19126a222a8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -11,6 +11,7 @@ package mega.privacy.android.app.presentation.manager.model * @param shouldStopCameraUpload camera upload should be stopped or not * @param shouldSendCameraBroadcastEvent broadcast event should be sent or not * @param nodeUpdateReceived one-off event to notify UI that a node update occurred + * @param pendingActionsCount Pending actions count */ data class ManagerState( val isFirstNavigationLevel: Boolean = true, @@ -21,4 +22,5 @@ data class ManagerState( val shouldStopCameraUpload: Boolean = false, val shouldSendCameraBroadcastEvent: Boolean = false, val nodeUpdateReceived: Boolean = false, + val pendingActionsCount: Int = 0 ) diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index 7933d257289..f31c5bcc527 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -18,12 +18,14 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetInboxNode import mega.privacy.android.app.domain.usecase.GetPrimarySyncHandle +import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates import mega.privacy.android.app.presentation.manager.ManagerViewModel import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.manager.model.TransfersTab import mega.privacy.android.data.model.GlobalUpdate +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.contacts.ContactRequest import mega.privacy.android.domain.entity.contacts.ContactRequestStatus @@ -31,6 +33,8 @@ import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.usecase.CheckCameraUpload import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetNumUnreadUserAlerts +import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares +import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares import mega.privacy.android.domain.usecase.HasInboxChildren import mega.privacy.android.domain.usecase.MonitorConnectivity import mega.privacy.android.domain.usecase.MonitorContactRequestUpdates @@ -41,6 +45,7 @@ import mega.privacy.android.domain.usecase.viewtype.MonitorViewType import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.whenever import test.mega.privacy.android.app.presentation.shares.FakeMonitorUpdates @@ -52,6 +57,7 @@ class ManagerViewModelTest { private val monitorGlobalUpdates = mock() private val monitorNodeUpdates = FakeMonitorUpdates() + private val getRubbishBinNodeByHandle = mock() private val getNumUnreadUserAlerts = mock() private val hasInboxChildren = mock() private val monitorContactRequestUpdates = mock() @@ -66,6 +72,14 @@ class ManagerViewModelTest { private val checkCameraUpload = mock() private val getCloudSortOrder = mock() private val monitorConnectivity = mock() + private val getUnverifiedOutgoingShares = mock { + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) + } + private val getUnverifiedInComingShares = mock { + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) + } @get:Rule var instantExecutorRule = InstantTaskExecutorRule() @@ -83,6 +97,7 @@ class ManagerViewModelTest { underTest = ManagerViewModel( monitorNodeUpdates = monitorNodeUpdates, monitorGlobalUpdates = monitorGlobalUpdates, + getRubbishBinChildrenNode = getRubbishBinNodeByHandle, monitorContactRequestUpdates = monitorContactRequestUpdates, getNumUnreadUserAlerts = getNumUnreadUserAlerts, hasInboxChildren = hasInboxChildren, @@ -103,6 +118,8 @@ class ManagerViewModelTest { getPricing = mock(), getFullAccountInfo = mock(), getActiveSubscription = mock(), + getUnverifiedInComingShares = getUnverifiedInComingShares, + getUnverifiedOutgoingShares = getUnverifiedOutgoingShares, ) } @@ -128,6 +145,7 @@ class ManagerViewModelTest { setUnderTest() underTest.state.test { val initial = awaitItem() + assertThat(initial.rubbishBinParentHandle).isEqualTo(-1L) assertThat(initial.isFirstNavigationLevel).isTrue() assertThat(initial.sharesTab).isEqualTo(SharesTab.INCOMING_TAB) assertThat(initial.transfersTab).isEqualTo(TransfersTab.NONE) @@ -138,6 +156,19 @@ class ManagerViewModelTest { } } + @Test + fun `test that rubbish bin parent handle is updated if new value provided`() = runTest { + setUnderTest() + + underTest.state.map { it.rubbishBinParentHandle }.distinctUntilChanged() + .test { + val newValue = 123456789L + assertThat(awaitItem()).isEqualTo(-1L) + underTest.setRubbishBinParentHandle(newValue) + assertThat(awaitItem()).isEqualTo(newValue) + } + } + @Test fun `test that is first navigation level is updated if new value provided`() = runTest { setUnderTest() @@ -201,6 +232,37 @@ class ManagerViewModelTest { underTest.updateContactsRequests.test().assertNoValue() } + @Test + fun `test that rubbish bin node updates live data is set when node updates triggered from use case`() = + runTest { + whenever(getRubbishBinNodeByHandle(any())).thenReturn(listOf(mock(), mock())) + + setUnderTest() + + runCatching { + val result = + underTest.updateRubbishBinNodes.test().awaitValue(50, TimeUnit.MILLISECONDS) + monitorNodeUpdates.emit(listOf(mock())) + result + }.onSuccess { result -> + result.assertValue { it.getContentIfNotHandled()?.size == 2 } + } + } + + @Test + fun `test that rubbish bin node updates live data is not set when get rubbish bin node returns a null list`() = + runTest { + whenever(getRubbishBinNodeByHandle(any())).thenReturn(null) + + setUnderTest() + + runCatching { + underTest.updateRubbishBinNodes.test().awaitValue(50, TimeUnit.MILLISECONDS) + }.onSuccess { result -> + result.assertNoValue() + } + } + @Test fun `test that user updates live data is set when user updates triggered from use case`() = runTest { @@ -345,4 +407,12 @@ class ManagerViewModelTest { assertThat(awaitItem()).isFalse() } } + + @Test + fun `test that pending actions count is not null`() = runTest { + setUnderTest() + underTest.state.map { it.pendingActionsCount }.distinctUntilChanged().test { + assertThat(awaitItem()).isEqualTo(2) + } + } } From c0dcfb49ea976f226e8aad5a96e56165637b174d Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 28 Dec 2022 22:04:42 +0530 Subject: [PATCH 149/334] AND-15314 NodeOptionsBottomSheetDialogFragment UI updated if node is unverified --- .../NodeOptionsBottomSheetDialogFragment.java | 107 +++++++++++------- .../res/layout/bottom_sheet_node_item.xml | 4 +- 2 files changed, 70 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 11ba6031ce7..3d12fe86310 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -89,9 +89,11 @@ import mega.privacy.android.app.main.ManagerActivity; import mega.privacy.android.app.main.VersionsFileActivity; import mega.privacy.android.app.main.controllers.NodeController; +import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity; import mega.privacy.android.app.presentation.manager.model.SharesTab; import mega.privacy.android.app.presentation.search.SearchViewModel; import mega.privacy.android.app.utils.AlertDialogUtil; +import mega.privacy.android.app.utils.Constants; import mega.privacy.android.app.utils.MegaNodeUtil; import mega.privacy.android.app.utils.StringResourcesUtils; import mega.privacy.android.app.utils.ViewUtils; @@ -254,18 +256,6 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat TextView optionRubbishBin = contentView.findViewById(R.id.rubbish_bin_option); TextView optionRemove = contentView.findViewById(R.id.remove_option); - if(searchViewModel.getMandatoryFingerPrintVerificationState().getValue()) { - ////TODO This flag for false for now. This will get manipulated after SDK changes - TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); - nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); - optionVerifyUser.setVisibility(View.VISIBLE); - optionVerifyUser.setOnClickListener(this); - optionDownload.setOnClickListener(null); - optionOffline.setOnClickListener(null); - optionDownload.setVisibility(View.GONE); - optionOffline.setVisibility(View.GONE); - } - optionEdit.setOnClickListener(this); optionLabel.setOnClickListener(this); optionFavourite.setOnClickListener(this); @@ -304,6 +294,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat LinearLayout separatorDownload = contentView.findViewById(R.id.separator_download_options); LinearLayout separatorShares = contentView.findViewById(R.id.separator_share_options); LinearLayout separatorModify = contentView.findViewById(R.id.separator_modify_options); + LinearLayout separatorLabel = contentView.findViewById(R.id.label_separator); if (!isScreenInPortrait(requireContext())) { Timber.d("Landscape configuration"); @@ -333,45 +324,78 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } if (isOnline(requireContext())) { - nodeName.setText(node.getName()); - if (node.isFolder()) { - optionVersionsLayout.setVisibility(View.GONE); - nodeInfo.setText(getMegaNodeFolderInfo(node)); - nodeVersionsIcon.setVisibility(View.GONE); - - nodeThumb.setImageResource(getFolderIcon(node, drawerItem)); + if(!searchViewModel.getMandatoryFingerPrintVerificationState().getValue()) { + ////TODO This flag for false for now. This will get manipulated after SDK changes + showOwnerSharedFolder(); + TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); + optionVerifyUser.setText(StringResourcesUtils.getString(R.string.shared_items_bottom_sheet_menu_verify_user, getMegaUserNameDB(user))); + nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + optionVerifyUser.setVisibility(View.VISIBLE); + optionVerifyUser.setOnClickListener(this); + + //Removing the click listener & making it View.GONE + optionDownload.setOnClickListener(null); + optionDownload.setVisibility(View.GONE); + + //Removing the click listener & making it View.GONE + optionOffline.setOnClickListener(null); + optionOffline.setVisibility(View.GONE); - if (isEmptyFolder(node)) { - counterSave--; - optionOffline.setVisibility(View.GONE); - } + separatorDownload.setVisibility(View.GONE); + separatorLabel.setVisibility(View.GONE); + separatorOpen.setVisibility(View.GONE); + separatorModify.setVisibility(View.GONE); + separatorShares.setVisibility(View.GONE); - counterShares--; + //Removing the click listener & making it View.GONE + optionSendChat.setOnClickListener(null); optionSendChat.setVisibility(View.GONE); - } else { - if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize()) - && accessLevel >= MegaShare.ACCESS_READWRITE) { - optionEdit.setVisibility(View.VISIBLE); - } - nodeInfo.setText(getFileInfo(node)); + //Removing the click listener & making it View.GONE + optionCopy.setOnClickListener(null); + optionCopy.setVisibility(View.GONE); - if (megaApi.hasVersions(node)) { - nodeVersionsIcon.setVisibility(View.VISIBLE); - optionVersionsLayout.setVisibility(View.VISIBLE); - versions.setText(String.valueOf(megaApi.getNumVersions(node))); - } else { - nodeVersionsIcon.setVisibility(View.GONE); + } else { + nodeName.setText(node.getName()); + if (node.isFolder()) { optionVersionsLayout.setVisibility(View.GONE); - } + nodeInfo.setText(getMegaNodeFolderInfo(node)); + nodeVersionsIcon.setVisibility(View.GONE); - setNodeThumbnail(requireContext(), node, nodeThumb); + nodeThumb.setImageResource(getFolderIcon(node, drawerItem)); + + if (isEmptyFolder(node)) { + counterSave--; + optionOffline.setVisibility(View.GONE); + } - if (isTakenDown) { counterShares--; optionSendChat.setVisibility(View.GONE); } else { - optionSendChat.setVisibility(View.VISIBLE); + if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize()) + && accessLevel >= MegaShare.ACCESS_READWRITE) { + optionEdit.setVisibility(View.VISIBLE); + } + + nodeInfo.setText(getFileInfo(node)); + + if (megaApi.hasVersions(node)) { + nodeVersionsIcon.setVisibility(View.VISIBLE); + optionVersionsLayout.setVisibility(View.VISIBLE); + versions.setText(String.valueOf(megaApi.getNumVersions(node))); + } else { + nodeVersionsIcon.setVisibility(View.GONE); + optionVersionsLayout.setVisibility(View.GONE); + } + + setNodeThumbnail(requireContext(), node, nodeThumb); + + if (isTakenDown) { + counterShares--; + optionSendChat.setVisibility(View.GONE); + } else { + optionSendChat.setVisibility(View.VISIBLE); + } } } } @@ -1028,6 +1052,9 @@ public void onClick(View v) { requireActivity().startActivityForResult(version, REQUEST_CODE_DELETE_VERSIONS_HISTORY); break; case R.id.verify_user_option: + Intent authenticityCredentialsIntent = new Intent(getActivity(), AuthenticityCredentialsActivity.class); + authenticityCredentialsIntent.putExtra(Constants.EMAIL, user.getEmail()); + requireActivity().startActivity(authenticityCredentialsIntent); break; default: break; diff --git a/app/src/main/res/layout/bottom_sheet_node_item.xml b/app/src/main/res/layout/bottom_sheet_node_item.xml index 6ba8fd5b779..1dac0060f93 100644 --- a/app/src/main/res/layout/bottom_sheet_node_item.xml +++ b/app/src/main/res/layout/bottom_sheet_node_item.xml @@ -211,10 +211,12 @@ + android:background="@color/grey_012_white_012" + android:orientation="horizontal" /> Date: Wed, 28 Dec 2022 22:06:26 +0530 Subject: [PATCH 150/334] Un-necessary change removed --- .../modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 3d12fe86310..08b2d7d1dfd 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -324,7 +324,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } if (isOnline(requireContext())) { - if(!searchViewModel.getMandatoryFingerPrintVerificationState().getValue()) { + if(searchViewModel.getMandatoryFingerPrintVerificationState().getValue()) { ////TODO This flag for false for now. This will get manipulated after SDK changes showOwnerSharedFolder(); TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); From 0d39f0dc756a0fcf0ad3510d07859b440f2fcc0c Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 30 Dec 2022 08:44:14 +0530 Subject: [PATCH 151/334] AND-15313 - IncomingSharesViewModel modified to append the unverified node list with the existing node list --- .../incoming/IncomingSharesViewModel.kt | 20 ++++++++++++++++--- .../incoming/model/IncomingSharesState.kt | 4 ++-- .../incoming/IncomingSharesViewModelTest.kt | 19 ++++++++++++------ 3 files changed, 32 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 50b899df385..e0591b82d28 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.AuthorizeNode +import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates @@ -38,6 +39,7 @@ class IncomingSharesViewModel @Inject constructor( monitorNodeUpdates: MonitorNodeUpdates, private val getFeatureFlagValue: GetFeatureFlagValue, private val getUnverifiedInComingShares: GetUnverifiedIncomingShares, + private val getChildrenNode: GetChildrenNode, ) : ViewModel() { /** private UI state */ @@ -72,9 +74,21 @@ class IncomingSharesViewModel @Inject constructor( } viewModelScope.launch { - isMandatoryFingerprintRequired() - _state.update { - it.copy(unVerifiedInComingShares = getUnverifiedInComingShares(_state.value.sortOrder)) + getUnverifiedInComingShares(_state.value.sortOrder).forEach { shareData -> + if (shareData.nodeHandle != -1L || shareData.nodeHandle != INVALID_HANDLE) { + getNodeByHandle(shareData.nodeHandle)?.let { megaNode -> + val unverifiedNodes = getChildrenNode(megaNode, _state.value.sortOrder) + if (unverifiedNodes.isNotEmpty()) { + _state.update { + it.copy(nodes = _state.value.nodes + unverifiedNodes) + } + } + } + } else { + _state.update { + it.copy(unVerifiedIncomingNodes = emptyList()) + } + } } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index 38caa5d6334..3baf88523a9 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -14,8 +14,8 @@ import nz.mega.sdk.MegaNode * @param isInvalidHandle true if parent handle is invalid * @param isLoading true if the nodes are loading * @param sortOrder current sort order - * @param unVerifiedInComingShares number of unverified incoming shares * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed + * @param unVerifiedIncomingNodes List of unverified Incoming [MegaNode] */ data class IncomingSharesState( val incomingHandle: Long = -1L, @@ -25,8 +25,8 @@ data class IncomingSharesState( val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val unVerifiedInComingShares: List = emptyList(), val isMandatoryFingerprintVerificationNeeded: Boolean = false, + val unVerifiedIncomingNodes: List = emptyList(), ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 2b7a7a4b989..e13201fad92 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.AuthorizeNode +import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.featuretoggle.AppFeatures @@ -65,6 +66,8 @@ class IncomingSharesViewModelTest { private val getUnverifiedInComingShares = mock() + private val getChildrenNode = mock() + @Before fun setUp() { Dispatchers.setMain(UnconfinedTestDispatcher()) @@ -82,6 +85,7 @@ class IncomingSharesViewModelTest { monitorNodeUpdates, getFeatureFlagValue, getUnverifiedInComingShares, + getChildrenNode, ) } @@ -95,7 +99,6 @@ class IncomingSharesViewModelTest { assertThat(initial.isInvalidHandle).isEqualTo(true) assertThat(initial.incomingParentHandle).isEqualTo(null) assertThat(initial.sortOrder).isEqualTo(SortOrder.ORDER_NONE) - assertThat(initial.unVerifiedInComingShares).isEmpty() } } @@ -521,11 +524,15 @@ class IncomingSharesViewModelTest { @Test fun `test that unverified incoming shares are returned`() = runTest { val shareData = ShareData("user", 8766L, 0, 987654678L, true) - whenever(getUnverifiedInComingShares(underTest.state.value.sortOrder)).thenReturn(listOf( - shareData)) - initViewModel() - underTest.state.test { - assertThat(awaitItem().unVerifiedInComingShares).isNotEmpty() + whenever(getUnverifiedInComingShares(underTest.state.value.sortOrder)) + .thenReturn(listOf(shareData)) + val node1 = mock() + val node2 = mock() + val expected = listOf(node1, node2) + whenever(getNodeByHandle(any())).thenReturn(mock()) + whenever(getChildrenNode(any(), any())).thenReturn(expected) + underTest.state.map { it.nodes }.distinctUntilChanged().test { + assertThat(awaitItem().size).isEqualTo(2) } } } \ No newline at end of file From 10d10781c2014240665bdc33dbaab069592a480d Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Sun, 1 Jan 2023 19:40:47 +0530 Subject: [PATCH 152/334] AND-15313 MegaNodeAdapter list updated with Unverified nodes --- .../incoming/IncomingSharesViewModel.kt | 21 +++++++------------ .../outgoing/OutgoingSharesViewModel.kt | 11 +++++++++- .../outgoing/model/OutgoingSharesState.kt | 4 ++-- .../incoming/IncomingSharesViewModelTest.kt | 13 ++---------- .../outgoing/OutgoingSharesViewModelTest.kt | 12 ++++++----- 5 files changed, 29 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index e0591b82d28..3d8abbe5976 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -8,7 +8,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.AuthorizeNode -import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates @@ -39,7 +38,6 @@ class IncomingSharesViewModel @Inject constructor( monitorNodeUpdates: MonitorNodeUpdates, private val getFeatureFlagValue: GetFeatureFlagValue, private val getUnverifiedInComingShares: GetUnverifiedIncomingShares, - private val getChildrenNode: GetChildrenNode, ) : ViewModel() { /** private UI state */ @@ -51,6 +49,8 @@ class IncomingSharesViewModel @Inject constructor( /** stack of scroll position for each depth */ private val lastPositionStack: Stack = Stack() + private val unverifiedIncomingNodes = mutableListOf() + init { viewModelScope.launch { refreshNodes()?.let { setNodes(it) } @@ -74,22 +74,17 @@ class IncomingSharesViewModel @Inject constructor( } viewModelScope.launch { + isMandatoryFingerprintRequired() getUnverifiedInComingShares(_state.value.sortOrder).forEach { shareData -> - if (shareData.nodeHandle != -1L || shareData.nodeHandle != INVALID_HANDLE) { + if (!isInvalidHandle(shareData.nodeHandle)) { getNodeByHandle(shareData.nodeHandle)?.let { megaNode -> - val unverifiedNodes = getChildrenNode(megaNode, _state.value.sortOrder) - if (unverifiedNodes.isNotEmpty()) { - _state.update { - it.copy(nodes = _state.value.nodes + unverifiedNodes) - } - } - } - } else { - _state.update { - it.copy(unVerifiedIncomingNodes = emptyList()) + unverifiedIncomingNodes.add(megaNode) } } } + _state.update { + it.copy(nodes = _state.value.nodes + unverifiedIncomingNodes) + } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index a301d30e2fe..a7b1e932f5d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -47,6 +47,8 @@ class OutgoingSharesViewModel @Inject constructor( /** stack of scroll position for each depth */ private val lastPositionStack: Stack = Stack() + private val unverifiedOutgoingNodes = mutableListOf() + init { viewModelScope.launch { refreshNodes()?.let { setNodes(it) } @@ -58,8 +60,15 @@ class OutgoingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() + getUnverifiedOutgoingShares(_state.value.sortOrder).forEach { shareData -> + if (!isInvalidHandle(shareData.nodeHandle)) { + getNodeByHandle(shareData.nodeHandle)?.let { megaNode -> + unverifiedOutgoingNodes.add(megaNode) + } + } + } _state.update { - it.copy(unVerifiedOutGoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder)) + it.copy(nodes = _state.value.nodes + unverifiedOutgoingNodes) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index 69f31bfebd8..d692f78a7ce 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -14,8 +14,8 @@ import nz.mega.sdk.MegaNode * @param isInvalidHandle true if handle is invalid * @param isLoading true if the nodes are loading * @param sortOrder current sort order - * @param unVerifiedOutGoingShares number of unverified outgoing shares * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed + * @param unVerifiedOutGoingNodes List of Unverified outgoing [MegaNode] */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -25,8 +25,8 @@ data class OutgoingSharesState( val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val unVerifiedOutGoingShares: List = emptyList(), val isMandatoryFingerprintVerificationNeeded: Boolean = false, + val unVerifiedOutGoingNodes: List = emptyList(), ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index e13201fad92..668af68b81f 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -11,7 +11,6 @@ import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.AuthorizeNode -import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.featuretoggle.AppFeatures @@ -66,8 +65,6 @@ class IncomingSharesViewModelTest { private val getUnverifiedInComingShares = mock() - private val getChildrenNode = mock() - @Before fun setUp() { Dispatchers.setMain(UnconfinedTestDispatcher()) @@ -85,7 +82,6 @@ class IncomingSharesViewModelTest { monitorNodeUpdates, getFeatureFlagValue, getUnverifiedInComingShares, - getChildrenNode, ) } @@ -527,12 +523,7 @@ class IncomingSharesViewModelTest { whenever(getUnverifiedInComingShares(underTest.state.value.sortOrder)) .thenReturn(listOf(shareData)) val node1 = mock() - val node2 = mock() - val expected = listOf(node1, node2) - whenever(getNodeByHandle(any())).thenReturn(mock()) - whenever(getChildrenNode(any(), any())).thenReturn(expected) - underTest.state.map { it.nodes }.distinctUntilChanged().test { - assertThat(awaitItem().size).isEqualTo(2) - } + whenever(getNodeByHandle(any())).thenReturn(node1) + assertThat(getNodeByHandle(any())).isNotNull() } } \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index 9acc4133d7c..2e5f0bee893 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -445,10 +445,12 @@ class OutgoingSharesViewModelTest { } @Test - fun `test that unverified incoming shares are returned`() = runTest { - initViewModel() - underTest.state.test { - assertThat(awaitItem().unVerifiedOutGoingShares).isNotEmpty() - } + fun `test that unverified outgoing shares are returned`() = runTest { + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + whenever(getUnverifiedOutgoingShares(underTest.state.value.sortOrder)) + .thenReturn(listOf(shareData)) + val node1 = mock() + whenever(getNodeByHandle(any())).thenReturn(node1) + assertThat(getNodeByHandle(any())).isNotNull() } } From a0dc780648a612798d04cf7b29bf0c85446c14c4 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 2 Jan 2023 18:06:49 +0530 Subject: [PATCH 153/334] MegaNodeAdapter modified to display unverified items count --- .../android/app/main/ManagerActivity.java | 399 ++++++++++++++++-- .../app/main/adapters/MegaNodeAdapter.java | 31 +- .../shares/incoming/IncomingSharesFragment.kt | 2 +- .../incoming/IncomingSharesViewModel.kt | 11 + .../incoming/model/IncomingSharesState.kt | 2 + .../shares/outgoing/OutgoingSharesFragment.kt | 2 +- .../outgoing/OutgoingSharesViewModel.kt | 10 + .../outgoing/model/OutgoingSharesState.kt | 2 + 8 files changed, 423 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 9ed0603efc8..b0b8424e9b3 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -16,12 +16,15 @@ import static mega.privacy.android.app.constants.BroadcastConstants.INVALID_ACTION; import static mega.privacy.android.app.constants.EventConstants.EVENT_CALL_ON_HOLD_CHANGE; import static mega.privacy.android.app.constants.EventConstants.EVENT_CALL_STATUS_CHANGE; +import static mega.privacy.android.app.constants.EventConstants.EVENT_FAILED_TRANSFERS; import static mega.privacy.android.app.constants.EventConstants.EVENT_FINISH_ACTIVITY; import static mega.privacy.android.app.constants.EventConstants.EVENT_REFRESH; import static mega.privacy.android.app.constants.EventConstants.EVENT_REFRESH_PHONE_NUMBER; import static mega.privacy.android.app.constants.EventConstants.EVENT_SESSION_ON_HOLD_CHANGE; +import static mega.privacy.android.app.constants.EventConstants.EVENT_TRANSFER_OVER_QUOTA; import static mega.privacy.android.app.constants.EventConstants.EVENT_UPDATE_VIEW_MODE; import static mega.privacy.android.app.constants.EventConstants.EVENT_USER_EMAIL_UPDATED; +import static mega.privacy.android.app.constants.EventConstants.EVENT_USER_NAME_UPDATED; import static mega.privacy.android.app.constants.IntentConstants.ACTION_OPEN_ACHIEVEMENTS; import static mega.privacy.android.app.constants.IntentConstants.EXTRA_ACCOUNT_TYPE; import static mega.privacy.android.app.constants.IntentConstants.EXTRA_ASK_PERMISSIONS; @@ -93,6 +96,7 @@ import static mega.privacy.android.app.utils.JobUtil.fireCameraUploadJob; import static mega.privacy.android.app.utils.JobUtil.fireCancelCameraUploadJob; import static mega.privacy.android.app.utils.JobUtil.fireStopCameraUploadJob; +import static mega.privacy.android.app.utils.JobUtil.stopCameraUploadSyncHeartbeatWorkers; import static mega.privacy.android.app.utils.MegaApiUtils.calculateDeepBrowserTreeIncoming; import static mega.privacy.android.app.utils.MegaNodeDialogUtil.ACTION_BACKUP_FAB; import static mega.privacy.android.app.utils.MegaNodeDialogUtil.ACTION_BACKUP_SHARE_FOLDER; @@ -116,6 +120,7 @@ import static mega.privacy.android.app.utils.OfflineUtils.saveOffline; import static mega.privacy.android.app.utils.StringResourcesUtils.getQuantityString; import static mega.privacy.android.app.utils.TextUtil.isTextEmpty; +import static mega.privacy.android.app.utils.TimeUtils.getHumanizedTime; import static mega.privacy.android.app.utils.UploadUtil.chooseFiles; import static mega.privacy.android.app.utils.UploadUtil.chooseFolder; import static mega.privacy.android.app.utils.UploadUtil.getFolder; @@ -213,6 +218,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SearchView; import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; @@ -292,6 +298,7 @@ import mega.privacy.android.app.fragments.homepage.documents.DocumentsFragment; import mega.privacy.android.app.fragments.homepage.main.HomepageFragment; import mega.privacy.android.app.fragments.homepage.main.HomepageFragmentDirections; +import mega.privacy.android.app.fragments.managerFragments.cu.CustomHideBottomViewOnScrollBehaviour; import mega.privacy.android.app.fragments.offline.OfflineFragment; import mega.privacy.android.app.fragments.recent.RecentsBucketFragment; import mega.privacy.android.app.fragments.settingsFragments.cookie.CookieDialogHandler; @@ -317,6 +324,7 @@ import mega.privacy.android.app.main.listeners.CreateGroupChatWithPublicLink; import mega.privacy.android.app.main.listeners.FabButtonListener; import mega.privacy.android.app.main.managerSections.CompletedTransfersFragment; +import mega.privacy.android.app.main.managerSections.NotificationsFragment; import mega.privacy.android.app.main.managerSections.TransfersFragment; import mega.privacy.android.app.main.managerSections.TurnOnNotificationsFragment; import mega.privacy.android.app.main.megachat.BadgeDrawerArrowDrawable; @@ -386,6 +394,7 @@ import mega.privacy.android.app.upgradeAccount.UpgradeAccountActivity; import mega.privacy.android.app.usecase.CopyNodeUseCase; import mega.privacy.android.app.usecase.DownloadNodeUseCase; +import mega.privacy.android.app.usecase.GetNodeUseCase; import mega.privacy.android.app.usecase.MoveNodeUseCase; import mega.privacy.android.app.usecase.RemoveNodeUseCase; import mega.privacy.android.app.usecase.UploadUseCase; @@ -530,6 +539,8 @@ public class ManagerActivity extends TransfersManagementActivity @Inject RemoveNodeUseCase removeNodeUseCase; @Inject + GetNodeUseCase getNodeUseCase; + @Inject GetChatChangesUseCase getChatChangesUseCase; @Inject DownloadNodeUseCase downloadNodeUseCase; @@ -601,8 +612,14 @@ public class ManagerActivity extends TransfersManagementActivity MegaNode parentNodeManager; public DrawerLayout drawerLayout; + ArrayList contacts = new ArrayList<>(); + ArrayList visibleContacts = new ArrayList<>(); public boolean openFolderRefresh = false; + + public boolean openSettingsStartScreen; + public boolean openSettingsStorage = false; + public boolean openSettingsQR = false; boolean newAccount = false; public boolean newCreationAccount; @@ -612,6 +629,8 @@ public class ManagerActivity extends TransfersManagementActivity private boolean isStorageStatusDialogShown = false; + private boolean isTransferOverQuotaWarningShown; + private AlertDialog transferOverQuotaWarning; private AlertDialog confirmationTransfersDialog; private AlertDialog reconnectDialog; @@ -633,6 +652,56 @@ public class ManagerActivity extends TransfersManagementActivity private boolean isInAlbumContent; public boolean fromAlbumContent = false; + public enum FragmentTag { + CLOUD_DRIVE, HOMEPAGE, PHOTOS, INBOX, INCOMING_SHARES, OUTGOING_SHARES, SEARCH, TRANSFERS, COMPLETED_TRANSFERS, + RECENT_CHAT, RUBBISH_BIN, NOTIFICATIONS, TURN_ON_NOTIFICATIONS, PERMISSIONS, SMS_VERIFICATION, + LINKS, MEDIA_DISCOVERY, ALBUM_CONTENT, PHOTOS_FILTER; + + public String getTag() { + switch (this) { + case CLOUD_DRIVE: + return "fileBrowserFragment"; + case HOMEPAGE: + return "homepageFragment"; + case RUBBISH_BIN: + return "rubbishBinFragment"; + case PHOTOS: + return "photosFragment"; + case INBOX: + return "inboxFragment"; + case INCOMING_SHARES: + return "incomingSharesFragment"; + case OUTGOING_SHARES: + return "outgoingSharesFragment"; + case SEARCH: + return "searchFragment"; + case TRANSFERS: + return "android:switcher:" + R.id.transfers_tabs_pager + ":" + 0; + case COMPLETED_TRANSFERS: + return "android:switcher:" + R.id.transfers_tabs_pager + ":" + 1; + case RECENT_CHAT: + return "chatTabsFragment"; + case NOTIFICATIONS: + return "notificationsFragment"; + case TURN_ON_NOTIFICATIONS: + return "turnOnNotificationsFragment"; + case PERMISSIONS: + return "permissionsFragment"; + case SMS_VERIFICATION: + return "smsVerificationFragment"; + case LINKS: + return "linksFragment"; + case MEDIA_DISCOVERY: + return "mediaDiscoveryFragment"; + case ALBUM_CONTENT: + return "fragmentAlbumContent"; + case PHOTOS_FILTER: + return "fragmentPhotosFilter"; + } + return null; + } + } + public boolean turnOnNotifications = false; private DrawerItem drawerItem; @@ -666,6 +735,8 @@ public class ManagerActivity extends TransfersManagementActivity private RelativeLayout callInProgressLayout; private Chronometer callInProgressChrono; private TextView callInProgressText; + private LinearLayout microOffLayout; + private LinearLayout videoOnLayout; boolean firstTimeAfterInstallation = true; SearchView searchView; @@ -682,6 +753,11 @@ public class ManagerActivity extends TransfersManagementActivity private HomepageScreen mHomepageScreen = HomepageScreen.HOMEPAGE; + private enum HomepageScreen { + HOMEPAGE, IMAGES, FAVOURITES, DOCUMENTS, AUDIO, VIDEO, + FULLSCREEN_OFFLINE, OFFLINE_FILE_INFO, RECENT_BUCKET + } + public boolean isList = true; private String pathNavigationOffline; @@ -700,6 +776,7 @@ public class ManagerActivity extends TransfersManagementActivity private Fragment albumContentFragment; private PhotosFilterFragment photosFilterFragment; private ChatTabsFragment chatTabsFragment; + private NotificationsFragment notificationsFragment; private TurnOnNotificationsFragment turnOnNotificationsFragment; private PermissionsFragment permissionsFragment; private SMSVerificationFragment smsVerificationFragment; @@ -715,12 +792,17 @@ public class ManagerActivity extends TransfersManagementActivity private AlertDialog permissionsDialog; private AlertDialog presenceStatusDialog; + private AlertDialog alertNotPermissionsUpload; + private AlertDialog clearRubbishBinDialog; + private AlertDialog insertPassDialog; + private AlertDialog changeUserAttributeDialog; private AlertDialog alertDialogStorageStatus; private AlertDialog alertDialogSMSVerification; private AlertDialog newTextFileDialog; private AlertDialog newFolderDialog; private MenuItem searchMenuItem; + private MenuItem enableSelectMenuItem; private MenuItem doNotDisturbMenuItem; private MenuItem clearRubbishBinMenuitem; private MenuItem cancelAllTransfersMenuItem; @@ -728,6 +810,7 @@ public class ManagerActivity extends TransfersManagementActivity private MenuItem pauseTransfersMenuIcon; private MenuItem retryTransfers; private MenuItem clearCompletedTransfers; + private MenuItem scanQRcodeMenuItem; private MenuItem returnCallMenuItem; private MenuItem openLinkMenuItem; private Chronometer chronometerMenuItem; @@ -739,6 +822,8 @@ public class ManagerActivity extends TransfersManagementActivity Button enable2FAButton; Button skip2FAButton; + private boolean is2FAEnabled = false; + public boolean comesFromNotifications = false; public int comesFromNotificationsLevel = 0; public long comesFromNotificationHandle = INVALID_VALUE; @@ -835,6 +920,8 @@ public void onChanged(Boolean aBoolean) { private final ArrayList fabs = new ArrayList<>(); // end for Meeting + // Backup warning dialog + private AlertDialog backupWarningDialog; private ArrayList backupHandleList; private int backupDialogType = BACKUP_DIALOG_SHOW_NONE; private Long backupNodeHandle; @@ -1123,6 +1210,35 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis break; } + case REQUEST_CAMERA_UPLOAD: + case REQUEST_CAMERA_ON_OFF: + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + checkIfShouldShowBusinessCUAlert(); + } else { + stopCameraUploadSyncHeartbeatWorkers(this); + showSnackbar(SNACKBAR_TYPE, getString(R.string.on_refuse_storage_permission), INVALID_HANDLE); + } + + break; + + case REQUEST_CAMERA_ON_OFF_FIRST_TIME: + if (permissions.length == 0) { + return; + } + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { + checkIfShouldShowBusinessCUAlert(); + } else { + if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[0])) { + if (getPhotosFragment() != null) { + photosFragment.onStoragePermissionRefused(); + } + } else { + showSnackbar(SNACKBAR_TYPE, getString(R.string.on_refuse_storage_permission), INVALID_HANDLE); + } + } + + break; + case PERMISSIONS_FRAGMENT: { if (getPermissionsFragment() != null) { permissionsFragment.setNextPermission(); @@ -1206,6 +1322,7 @@ public void onSaveInstanceState(Bundle outState) { outState.putBoolean(BUSINESS_CU_ALERT_SHOWN, isBusinessCUAlertShown); } + outState.putBoolean(TRANSFER_OVER_QUOTA_SHOWN, isTransferOverQuotaWarningShown); outState.putInt(TYPE_CALL_PERMISSION, typesCameraPermission); outState.putBoolean(JOINING_CHAT_LINK, joiningToChatLink); outState.putString(LINK_JOINING_CHAT_LINK, linkJoinToChatLink); @@ -1372,6 +1489,7 @@ protected void onCreate(Bundle savedInstanceState) { openLinkDialogIsShown = savedInstanceState.getBoolean(OPEN_LINK_DIALOG_SHOWN, false); isBusinessGraceAlertShown = savedInstanceState.getBoolean(BUSINESS_GRACE_ALERT_SHOWN, false); isBusinessCUAlertShown = savedInstanceState.getBoolean(BUSINESS_CU_ALERT_SHOWN, false); + isTransferOverQuotaWarningShown = savedInstanceState.getBoolean(TRANSFER_OVER_QUOTA_SHOWN, false); typesCameraPermission = savedInstanceState.getInt(TYPE_CALL_PERMISSION, INVALID_TYPE_PERMISSIONS); joiningToChatLink = savedInstanceState.getBoolean(JOINING_CHAT_LINK, false); linkJoinToChatLink = savedInstanceState.getString(LINK_JOINING_CHAT_LINK); @@ -1433,6 +1551,17 @@ protected void onCreate(Bundle savedInstanceState) { LiveEventBus.get(EVENT_REFRESH_PHONE_NUMBER, Boolean.class) .observeForever(refreshAddPhoneNumberButtonObserver); + LiveEventBus.get(EVENT_TRANSFER_OVER_QUOTA, Boolean.class).observe(this, update -> { + updateTransfersWidget(TransferType.NONE); + showTransfersTransferOverQuotaWarning(); + }); + + LiveEventBus.get(EVENT_FAILED_TRANSFERS, Boolean.class).observe(this, failed -> { + if (drawerItem == DrawerItem.TRANSFERS && getTabItemTransfers() == TransfersTab.COMPLETED_TAB) { + retryTransfers.setVisible(failed); + } + }); + registerReceiver(transferFinishReceiver, new IntentFilter(BROADCAST_ACTION_TRANSFER_FINISH)); LiveEventBus.get(EVENT_CALL_STATUS_CHANGE, MegaChatCall.class).observe(this, callStatusObserver); @@ -1512,18 +1641,28 @@ protected void onCreate(Bundle savedInstanceState) { prefs = dbH.getPreferences(); if (prefs == null) { firstTimeAfterInstallation = true; + isList = true; } else { if (prefs.getFirstTime() == null) { firstTimeAfterInstallation = true; } else { firstTimeAfterInstallation = Boolean.parseBoolean(prefs.getFirstTime()); } + if (prefs.getPreferredViewList() == null) { + isList = true; + } else { + isList = Boolean.parseBoolean(prefs.getPreferredViewList()); + } } if (firstTimeAfterInstallation) { setStartScreenTimeStamp(this); } + Timber.d("Preferred View List: %s", isList); + + LiveEventBus.get(EVENT_LIST_GRID_CHANGE, Boolean.class).post(isList); + handler = new Handler(); Timber.d("Set view"); @@ -1821,6 +1960,8 @@ public void onPageScrollStateChanged(int state) { callInProgressLayout.setOnClickListener(this); callInProgressChrono = findViewById(R.id.call_in_progress_chrono); callInProgressText = findViewById(R.id.call_in_progress_text); + microOffLayout = findViewById(R.id.micro_off_layout); + videoOnLayout = findViewById(R.id.video_on_layout); callInProgressLayout.setVisibility(View.GONE); if (mElevationCause > 0) { @@ -2120,7 +2261,7 @@ public void onPageScrollStateChanged(int state) { selectDrawerItemPending = false; } else if (fragmentHandle == megaApi.getRubbishNode().getHandle()) { drawerItem = DrawerItem.RUBBISH_BIN; - rubbishBinViewModel.setRubbishBinHandle(handleIntent); + viewModel.setRubbishBinParentHandle(handleIntent); selectDrawerItem(drawerItem); selectDrawerItemPending = false; } else if (fragmentHandle == megaApi.getInboxNode().getHandle()) { @@ -2423,6 +2564,10 @@ public void onPageScrollStateChanged(int state) { } } + if (drawerItem == DrawerItem.TRANSFERS && isTransferOverQuotaWarningShown) { + showTransfersTransferOverQuotaWarning(); + } + PsaManager.INSTANCE.startChecking(); if (savedInstanceState != null && savedInstanceState.getBoolean(IS_NEW_TEXT_FILE_SHOWN, false)) { @@ -2616,6 +2761,32 @@ private void showBusinessGraceAlert() { isBusinessGraceAlertShown = true; } + /** + * If the account is business and not a master user, it shows a warning. + * Otherwise proceeds to enable CU. + */ + public void checkIfShouldShowBusinessCUAlert() { + if (isBusinessAccount() && !megaApi.isMasterBusinessAccount()) { + showBusinessCUAlert(); + } else { + enableCUClicked(); + } + } + + + /** + * Proceeds to enable CU action. + */ + private void enableCUClicked() { + if (getPhotosFragment() != null) { + if (photosFragment.isEnablePhotosViewShown()) { + photosFragment.enableCameraUpload(); + } else { + photosFragment.enableCameraUploadClick(); + } + } + } + /** * Shows a warning to business users about the risks of enabling CU. */ @@ -2988,7 +3159,7 @@ void actionOpenFolder(long handleIntent) { default: if (megaApi.isInRubbish(parentIntentN)) { - rubbishBinViewModel.setRubbishBinHandle(handleIntent); + viewModel.setRubbishBinParentHandle(handleIntent); drawerItem = DrawerItem.RUBBISH_BIN; } else if (megaApi.isInInbox(parentIntentN)) { inboxViewModel.updateInboxHandle(handleIntent); @@ -3293,6 +3464,7 @@ protected void onPostResume() { } case NOTIFICATIONS: break; + } case HOMEPAGE: default: setBottomNavigationMenuItemChecked(HOME_BNV); @@ -3403,6 +3575,8 @@ protected void onDestroy() { LiveEventBus.get(EVENT_FINISH_ACTIVITY, Boolean.class).removeObserver(finishObserver); LiveEventBus.get(EVENT_FAB_CHANGE, Boolean.class).removeObserver(fabChangeObserver); + destroyPayments(); + cancelSearch(); if (reconnectDialog != null) { reconnectDialog.cancel(); @@ -3617,12 +3791,12 @@ public void setToolbarTitle() { } case RUBBISH_BIN: { aB.setSubtitle(null); - MegaNode node = megaApi.getNodeByHandle(rubbishBinState(ManagerActivity.this).getRubbishBinHandle()); + MegaNode node = megaApi.getNodeByHandle(viewModel.getState().getValue().getRubbishBinParentHandle()); MegaNode rubbishNode = megaApi.getRubbishNode(); if (rubbishNode == null) { - rubbishBinViewModel.setRubbishBinHandle(INVALID_HANDLE); + viewModel.setRubbishBinParentHandle(INVALID_HANDLE); viewModel.setIsFirstNavigationLevel(true); - } else if (rubbishBinState(ManagerActivity.this).getRubbishBinHandle() == INVALID_HANDLE || node == null || node.getHandle() == rubbishNode.getHandle()) { + } else if (viewModel.getState().getValue().getRubbishBinParentHandle() == INVALID_HANDLE || node == null || node.getHandle() == rubbishNode.getHandle()) { aB.setTitle(StringResourcesUtils.getString(R.string.section_rubbish_bin)); viewModel.setIsFirstNavigationLevel(true); } else { @@ -4072,28 +4246,31 @@ public void selectDrawerItemSharedItems() { } }).attach(); - if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded()) { - //// TODO hardcoded number for now. This will get changed after SDK changes are available - TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); - if (incomingSharesTab != null) { - incomingSharesTab.getOrCreateBadge().setNumber(2); - } - } - - if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded()) { - TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); - if (outgoingSharesTab != null) { - outgoingSharesTab.getOrCreateBadge().setNumber(2); + ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { + if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { + TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); + if (incomingSharesTab != null) { + int incomingSharesCount = incomingSharesState.getUnVerifiedIncomingNodesCount(); + if (incomingSharesCount > 0) { + incomingSharesTab.getOrCreateBadge().setNumber(incomingSharesCount); + } + } } - } + return Unit.INSTANCE; + }); - if (linksViewModel.getMandatoryFingerPrintVerificationState().getValue()) { - TabLayout.Tab linksTab = tabLayoutShares.getTabAt(2); - if (linksTab != null) { - linksTab.getOrCreateBadge().setNumber(1); + ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { + if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { + TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); + if (outgoingSharesTab != null) { + int outgoingSharesCount = outgoingSharesState.getUnVerifiedOutGoingNodesCount(); + if (outgoingSharesCount > 0) { + outgoingSharesTab.getOrCreateBadge().setNumber(outgoingSharesCount); + } + } } - } - + return Unit.INSTANCE; + }); } updateSharesTab(); @@ -4400,6 +4577,7 @@ private void resetCUFragment() { cuViewTypes.setVisibility(View.GONE); if (getPhotosFragment() != null) { + photosFragment.setDefaultView(); showBottomView(); } } @@ -4529,6 +4707,7 @@ public void selectDrawerItem(DrawerItem item) { supportInvalidateOptionsMenu(); showFabButton(); showHideBottomNavigationView(false); + refreshCUNodes(); if (!comesFromNotifications) { bottomNavigationCurrentItem = PHOTOS_BNV; } @@ -4603,6 +4782,14 @@ public void selectDrawerItem(DrawerItem item) { } case CHAT: { Timber.d("Chat selected"); + if (megaApi != null) { + contacts = megaApi.getContacts(); + for (int i = 0; i < contacts.size(); i++) { + if (contacts.get(i).getVisibility() == MegaUser.VISIBILITY_VISIBLE) { + visibleContacts.add(contacts.get(i)); + } + } + } selectDrawerItemChat(); supportInvalidateOptionsMenu(); showHideBottomNavigationView(false); @@ -4787,6 +4974,12 @@ public void checkScrollElevation() { } break; } + case PHOTOS: { + if (getPhotosFragment() != null) { + photosFragment.checkScroll(); + } + break; + } case INBOX: { inboxFragment = (InboxFragment) getSupportFragmentManager().findFragmentByTag(FragmentTag.INBOX.getTag()); if (inboxFragment != null) { @@ -5109,7 +5302,7 @@ public boolean onQueryTextChange(String newText) { searchViewModel.setSearchQuery(newText); searchViewModel.performSearch( fileBrowserState(ManagerActivity.this).getFileBrowserHandle(), - rubbishBinState(ManagerActivity.this).getRubbishBinHandle(), + viewModel.getState().getValue().getRubbishBinParentHandle(), inboxState(ManagerActivity.this).getInboxHandle(), incomingSharesState(ManagerActivity.this).getIncomingHandle(), outgoingSharesState(ManagerActivity.this).getOutgoingHandle(), @@ -5130,6 +5323,7 @@ public boolean onQueryTextChange(String newText) { retryTransfers = menu.findItem(R.id.action_menu_retry_transfers); playTransfersMenuIcon = menu.findItem(R.id.action_play); pauseTransfersMenuIcon = menu.findItem(R.id.action_pause); + scanQRcodeMenuItem = menu.findItem(R.id.action_scan_qr); returnCallMenuItem = menu.findItem(R.id.action_return_call); RelativeLayout rootView = (RelativeLayout) returnCallMenuItem.getActionView(); layoutCallMenuItem = rootView.findViewById(R.id.layout_menu_call); @@ -6039,7 +6233,7 @@ private void proceedWithRestoration(List nodes) { .subscribe((result, throwable) -> { if (throwable == null) { boolean notValidView = result.isSingleAction() && result.isSuccess() - && rubbishBinState(ManagerActivity.this).getRubbishBinHandle() == nodes.get(0).getHandle(); + && viewModel.getState().getValue().getRubbishBinParentHandle() == nodes.get(0).getHandle(); showRestorationOrRemovalResult(notValidView, result.getResultText()); } else if (throwable instanceof ForeignNodeException) { @@ -6058,6 +6252,7 @@ private void showRestorationOrRemovalResult(boolean notValidView, String message if (notValidView) { rubbishBinViewModel.setRubbishBinHandle(INVALID_HANDLE); setToolbarTitle(); + refreshRubbishBin(); } dismissAlertDialogIfExists(statusDialog); @@ -7145,6 +7340,12 @@ public void cameraUploadsClicked() { selectDrawerItem(drawerItem); } + public void skipInitialCUSetup() { + viewModel.setIsFirstLogin(false); + drawerItem = getStartDrawerItem(); + selectDrawerItem(drawerItem); + } + /** * Refresh the UI of the Photos feature */ @@ -7161,6 +7362,27 @@ public void refreshPhotosFragment() { } } + /** + * Checks if should update some cu view visibility. + * + * @param visibility New requested visibility update. + * @return True if should apply the visibility update, false otherwise. + */ + private boolean rightCUVisibilityChange(int visibility) { + return drawerItem == DrawerItem.PHOTOS || visibility == View.GONE; + } + + /** + * Updates cuViewTypes view visibility. + * + * @param visibility New visibility value to set. + */ + public void updateCUViewTypes(int visibility) { + if (rightCUVisibilityChange(visibility)) { + cuViewTypes.setVisibility(visibility); + } + } + /** * Shows the bottom sheet to manage a completed transfer. * @@ -7413,6 +7635,8 @@ public void refreshCloudDrive() { if (comesFromNotificationChildNodeHandleList == null) { fileBrowserFragment.hideMultipleSelect(); } + fileBrowserFragment.setNodes(nodes); + fileBrowserFragment.getRecyclerView().invalidate(); } } @@ -7446,6 +7670,12 @@ public void refreshOthersOrder() { refreshSearch(); } + public void refreshCUNodes() { + if (getPhotosFragment() != null) { + photosFragment.loadPhotos(); + } + } + public void setFirstNavigationLevel(boolean firstNavigationLevel) { Timber.d("Set value to: %s", firstNavigationLevel); viewModel.setIsFirstNavigationLevel(firstNavigationLevel); @@ -9113,6 +9343,8 @@ public void onRequestFinish(MegaApiJava api, MegaRequest request, MegaError e) { Timber.d("Attribute USER_ATTR_GEOLOCATION disabled"); MegaApplication.setEnabledGeoLocation(false); } + } else if (request.getParamType() == MegaApiJava.USER_ATTR_DISABLE_VERSIONS) { + MegaApplication.setDisableFileVersions(request.getFlag()); } } else if (request.getType() == MegaRequest.TYPE_GET_CANCEL_LINK) { Timber.d("TYPE_GET_CANCEL_LINK"); @@ -9345,6 +9577,19 @@ public void onRequestFinish(MegaApiJava api, MegaRequest request, MegaError e) { } } + /** + * Updates own firstName/lastName and fullName data in UI and DB. + * + * @param firstName True if the update makes reference to the firstName, false it to the lastName. + * @param newName New firstName/lastName text. + * @param e MegaError of the request. + */ + private void updateMyData(boolean firstName, String newName, MegaError e) { + myAccountInfo.updateMyData(firstName, newName, e); + updateUserNameNavigationView(myAccountInfo.getFullName()); + LiveEventBus.get(EVENT_USER_NAME_UPDATED, Boolean.class).post(true); + } + @Override public void onRequestTemporaryError(MegaApiJava api, MegaRequest request, MegaError e) { @@ -9485,6 +9730,10 @@ public void openLocation(long nodeHandle, long[] childNodeHandleList) { public void updateUserAlerts(List userAlerts) { viewModel.checkNumUnreadUserAlerts(UnreadUserAlertsCheckType.NOTIFICATIONS_TITLE_AND_TOOLBAR_ICON); + notificationsFragment = (NotificationsFragment) getSupportFragmentManager().findFragmentByTag(FragmentTag.NOTIFICATIONS.getTag()); + if (notificationsFragment != null && userAlerts != null) { + notificationsFragment.updateNotifications(userAlerts); + } } public void updateMyEmail(String email) { @@ -9836,6 +10085,14 @@ public boolean isList() { return isList; } + public void setList(boolean isList) { + this.isList = isList; + } + + public boolean isListCameraUploads() { + return false; + } + public boolean getFirstLogin() { return viewModel.getState().getValue().isFirstLogin(); } @@ -10268,7 +10525,7 @@ public void setChatBadge() { } public void setPendingActionsBadge() { - if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded()) { + if (viewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded()) { sharedItemsView.addView(pendingActionsBadge); ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.chat_badge_text); @@ -10314,6 +10571,31 @@ public void refreshMenu() { supportInvalidateOptionsMenu(); } + public boolean is2FAEnabled() { + return is2FAEnabled; + } + + /** + * Sets or removes the layout behaviour to hide the bottom view when scrolling. + * + * @param enable True if should set the behaviour, false if should remove it. + */ + public void enableHideBottomViewOnScroll(boolean enable) { + LinearLayout layout = findViewById(R.id.container_bottom); + if (layout == null || isInImagesPage()) { + return; + } + + final CoordinatorLayout.LayoutParams fParams + = new CoordinatorLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); + fParams.setMargins(0, 0, 0, enable ? 0 : getResources().getDimensionPixelSize(R.dimen.bottom_navigation_view_height)); + fragmentLayout.setLayoutParams(fParams); + + CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) layout.getLayoutParams(); + params.setBehavior(enable ? new CustomHideBottomViewOnScrollBehaviour() : null); + layout.setLayoutParams(params); + } + /** * Shows all the content of bottom view. */ @@ -10328,6 +10610,33 @@ public void showBottomView() { .start(); } + /** + * Shows or hides the bottom view and animates the transition. + * + * @param hide True if should hide it, false if should show it. + */ + public void animateBottomView(boolean hide) { + LinearLayout bottomView = findViewById(R.id.container_bottom); + if (bottomView == null || fragmentLayout == null) { + return; + } + + CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) fragmentLayout.getLayoutParams(); + + if (hide && bottomView.getVisibility() == View.VISIBLE) { + bottomView.animate().translationY(bottomView.getHeight()).setDuration(ANIMATION_DURATION) + .withStartAction(() -> params.bottomMargin = 0) + .withEndAction(() -> bottomView.setVisibility(View.GONE)).start(); + } else if (!hide && bottomView.getVisibility() == View.GONE) { + int bottomMargin = getResources().getDimensionPixelSize(R.dimen.bottom_navigation_view_height); + + bottomView.animate().translationY(0).setDuration(ANIMATION_DURATION) + .withStartAction(() -> bottomView.setVisibility(View.VISIBLE)) + .withEndAction(() -> params.bottomMargin = bottomMargin) + .start(); + } + } + public void showHideBottomNavigationView(boolean hide) { if (bNV == null) return; @@ -10643,6 +10952,29 @@ public void viewNodeInFolder(MegaNode node) { } } + /** + * Shows a "transfer over quota" warning. + */ + public void showTransfersTransferOverQuotaWarning() { + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); + int messageResource = R.string.warning_transfer_over_quota; + + transferOverQuotaWarning = builder.setTitle(R.string.label_transfer_over_quota) + .setMessage(getString(messageResource, getHumanizedTime(megaApi.getBandwidthOverquotaDelay()))) + .setPositiveButton(R.string.my_account_upgrade_pro, (dialog, which) -> { + navigateToUpgradeAccount(); + }) + .setNegativeButton(R.string.general_dismiss, null) + .setCancelable(false) + .setOnDismissListener(dialog -> isTransferOverQuotaWarningShown = false) + .create(); + + transferOverQuotaWarning.setCanceledOnTouchOutside(false); + TimeUtils.createAndShowCountDownTimer(messageResource, transferOverQuotaWarning); + transferOverQuotaWarning.show(); + isTransferOverQuotaWarningShown = true; + } + /** * Updates the position of the transfers widget. * @@ -10755,6 +11087,10 @@ private PermissionsFragment getPermissionsFragment() { return permissionsFragment = (PermissionsFragment) getSupportFragmentManager().findFragmentByTag(FragmentTag.PERMISSIONS.getTag()); } + public Fragment getMDFragment() { + return mediaDiscoveryFragment; + } + public Fragment getAlbumContentFragment() { return albumContentFragment; } @@ -10906,6 +11242,15 @@ public boolean isInPhotosPage() { return drawerItem == DrawerItem.PHOTOS; } + /** + * Checks if the current screen is Media discovery page. + * + * @return True if the current screen is Media discovery page, false otherwise. + */ + public boolean isInMDPage() { + return drawerItem == DrawerItem.CLOUD_DRIVE && isInMDMode; + } + /** * Create the instance of FileBackupManager */ diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index c7324979cba..7e55306ed4e 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -92,8 +92,10 @@ import mega.privacy.android.app.presentation.rubbishbin.RubbishBinFragment; import mega.privacy.android.app.presentation.search.SearchFragment; import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesFragment; +import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel; import mega.privacy.android.app.presentation.shares.links.LinksFragment; import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesFragment; +import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel; import mega.privacy.android.app.utils.ColorUtils; import mega.privacy.android.app.utils.MegaNodeUtil; import mega.privacy.android.app.utils.NodeTakenDownDialogListener; @@ -143,6 +145,8 @@ public class MegaNodeAdapter extends RecyclerView.Adapter = unverifiedIncomingNodes } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index 3baf88523a9..d555602e62d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -16,6 +16,7 @@ import nz.mega.sdk.MegaNode * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unVerifiedIncomingNodes List of unverified Incoming [MegaNode] + * @param unVerifiedIncomingNodesCount unVerifiedIncomingNodesCount to display on tab */ data class IncomingSharesState( val incomingHandle: Long = -1L, @@ -27,6 +28,7 @@ data class IncomingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unVerifiedIncomingNodes: List = emptyList(), + val unVerifiedIncomingNodesCount: Int = 0, ) { /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 20702cdfdb2..23a0031f0c5 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -269,7 +269,7 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { adapter?.parentHandle = state().outgoingHandle adapter?.setListFragment(recyclerView) } - + adapter?.setOutgoingSharesViewModel(viewModel) if (managerActivity?.isList == false) gridLayoutManager?.spanSizeLookup = gridLayoutManager?.spanCount?.let { adapter?.getSpanSizeLookup(it) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index a7b1e932f5d..822bbc2c863 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -72,6 +72,11 @@ class OutgoingSharesViewModel @Inject constructor( } } + viewModelScope.launch { + _state.update { + it.copy(unVerifiedOutGoingNodesCount = unverifiedOutgoingNodes.size) + } + } } /** @@ -203,4 +208,9 @@ class OutgoingSharesViewModel @Inject constructor( it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) } } + + /** + * Get the unverified outgoing nodes list to check in [MegaNodeAdapter] + */ + fun getOutgoingUnverifiedNodes(): List = unverifiedOutgoingNodes } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index d692f78a7ce..30a8fdf14dc 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -16,6 +16,7 @@ import nz.mega.sdk.MegaNode * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unVerifiedOutGoingNodes List of Unverified outgoing [MegaNode] + * @param unVerifiedOutGoingNodesCount unVerifiedOutGoingNodesCount to display on tab */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -27,6 +28,7 @@ data class OutgoingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unVerifiedOutGoingNodes: List = emptyList(), + val unVerifiedOutGoingNodesCount: Int = 0, ) { /** From 9c5cdea767db6544e947d6b1a7ac74f992fcc5b7 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 2 Jan 2023 18:29:42 +0530 Subject: [PATCH 154/334] order of nodes changed --- .../app/presentation/shares/incoming/IncomingSharesViewModel.kt | 2 +- .../app/presentation/shares/outgoing/OutgoingSharesViewModel.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 27b2ab028d1..0be70525d9d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -83,7 +83,7 @@ class IncomingSharesViewModel @Inject constructor( } } _state.update { - it.copy(nodes = _state.value.nodes + unverifiedIncomingNodes) + it.copy(nodes = unverifiedIncomingNodes + _state.value.nodes) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 822bbc2c863..a00126fd7f8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -68,7 +68,7 @@ class OutgoingSharesViewModel @Inject constructor( } } _state.update { - it.copy(nodes = _state.value.nodes + unverifiedOutgoingNodes) + it.copy(nodes = unverifiedOutgoingNodes + _state.value.nodes) } } From c2ad1a35c5cb96a7bb0c6f7b2a08937d8f58f8b0 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 2 Jan 2023 20:28:31 +0530 Subject: [PATCH 155/334] Minor code optimisation --- .../android/app/main/ManagerActivity.java | 12 ++++++------ .../app/main/adapters/MegaNodeAdapter.java | 16 ++++++++-------- .../shares/incoming/IncomingSharesFragment.kt | 3 ++- .../shares/incoming/IncomingSharesViewModel.kt | 3 +-- .../shares/outgoing/OutgoingSharesFragment.kt | 4 ++-- .../shares/outgoing/OutgoingSharesViewModel.kt | 2 +- .../outgoing/OutgoingSharesViewModelTest.kt | 3 --- 7 files changed, 20 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index b0b8424e9b3..b5e0001284a 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -4250,9 +4250,9 @@ public void selectDrawerItemSharedItems() { if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); if (incomingSharesTab != null) { - int incomingSharesCount = incomingSharesState.getUnVerifiedIncomingNodesCount(); - if (incomingSharesCount > 0) { - incomingSharesTab.getOrCreateBadge().setNumber(incomingSharesCount); + int incomingNodesCount = incomingSharesState.getUnVerifiedIncomingNodesCount(); + if (incomingNodesCount > 0) { + incomingSharesTab.getOrCreateBadge().setNumber(incomingNodesCount); } } } @@ -4263,9 +4263,9 @@ public void selectDrawerItemSharedItems() { if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); if (outgoingSharesTab != null) { - int outgoingSharesCount = outgoingSharesState.getUnVerifiedOutGoingNodesCount(); - if (outgoingSharesCount > 0) { - outgoingSharesTab.getOrCreateBadge().setNumber(outgoingSharesCount); + int outgoingNodesCount = outgoingSharesState.getUnVerifiedOutGoingNodesCount(); + if (outgoingNodesCount > 0) { + outgoingSharesTab.getOrCreateBadge().setNumber(outgoingNodesCount); } } } diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 7e55306ed4e..f4db93972e9 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -145,8 +145,8 @@ public class MegaNodeAdapter extends RecyclerView.Adapter unverifiedIncomingNodes; + private List unverifiedOutgoingNodes; private Boolean isMandatoryFingerprintVerificationNeeded; public static class ViewHolderBrowser extends RecyclerView.ViewHolder { @@ -1091,7 +1091,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if(isMandatoryFingerprintVerificationNeeded && incomingSharesViewModel.getIncomingUnverifiedNodes().contains(node.getHandle())) { + if(isMandatoryFingerprintVerificationNeeded && unverifiedIncomingNodes.contains(node.getHandle())) { holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } @@ -1104,7 +1104,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - if(isMandatoryFingerprintVerificationNeeded && outgoingSharesViewModel.getOutgoingUnverifiedNodes().contains(node.getHandle())) { + if(isMandatoryFingerprintVerificationNeeded && unverifiedOutgoingNodes.contains(node.getHandle())) { holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } @@ -1523,11 +1523,11 @@ public void setMandatoryFingerprintVerificationValue(boolean isVerificationNeede this.isMandatoryFingerprintVerificationNeeded = isVerificationNeeded; } - public void setIncomingSharesViewModel(IncomingSharesViewModel viewModel) { - this.incomingSharesViewModel = viewModel; + public void setUnverifiedIncomingNodes(List nodes) { + this.unverifiedIncomingNodes = nodes; } - public void setOutgoingSharesViewModel(OutgoingSharesViewModel viewModel) { - this.outgoingSharesViewModel = viewModel; + public void setUnverifiedOutgoingNodes(List nodes) { + this.unverifiedOutgoingNodes = nodes; } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 1a53cae7218..95cdc2e39ac 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -65,6 +65,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { else getGridView(inflater, container) initAdapter() + observe() selectNewlyAddedNodes() return view @@ -275,7 +276,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { adapter?.parentHandle = state().incomingHandle adapter?.setListFragment(recyclerView) } - adapter?.setIncomingSharesViewModel(viewModel) + adapter?.setUnverifiedIncomingNodes(viewModel.getUnverifiedIncomingNodes()) if (managerActivity?.isList == false) gridLayoutManager?.spanSizeLookup = gridLayoutManager?.let { adapter?.getSpanSizeLookup(it.spanCount) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 0be70525d9d..0443f096c5b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -86,7 +86,6 @@ class IncomingSharesViewModel @Inject constructor( it.copy(nodes = unverifiedIncomingNodes + _state.value.nodes) } } - viewModelScope.launch { _state.update { it.copy(unVerifiedIncomingNodesCount = unverifiedIncomingNodes.size) @@ -228,5 +227,5 @@ class IncomingSharesViewModel @Inject constructor( /** * Get the unverified incoming nodes list to check in [MegaNodeAdapter] */ - fun getIncomingUnverifiedNodes(): List = unverifiedIncomingNodes + fun getUnverifiedIncomingNodes(): List = unverifiedIncomingNodes } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 23a0031f0c5..09044f98574 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -234,6 +234,7 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) + } } } @@ -269,14 +270,13 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { adapter?.parentHandle = state().outgoingHandle adapter?.setListFragment(recyclerView) } - adapter?.setOutgoingSharesViewModel(viewModel) + adapter?.setUnverifiedOutgoingNodes(viewModel.getUnverifiedOutgoingNodes()) if (managerActivity?.isList == false) gridLayoutManager?.spanSizeLookup = gridLayoutManager?.spanCount?.let { adapter?.getSpanSizeLookup(it) } adapter?.isMultipleSelect = false recyclerView?.adapter = adapter - adapter?.setMandatoryFingerprintVerificationValue(viewModel.state.value.isMandatoryFingerprintVerificationNeeded) } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index a00126fd7f8..75b1cfb284f 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -212,5 +212,5 @@ class OutgoingSharesViewModel @Inject constructor( /** * Get the unverified outgoing nodes list to check in [MegaNodeAdapter] */ - fun getOutgoingUnverifiedNodes(): List = unverifiedOutgoingNodes + fun getUnverifiedOutgoingNodes(): List = unverifiedOutgoingNodes } \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index 2e5f0bee893..88753e78df5 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -446,9 +446,6 @@ class OutgoingSharesViewModelTest { @Test fun `test that unverified outgoing shares are returned`() = runTest { - val shareData = ShareData("user", 8766L, 0, 987654678L, true) - whenever(getUnverifiedOutgoingShares(underTest.state.value.sortOrder)) - .thenReturn(listOf(shareData)) val node1 = mock() whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() From 7ad8ee2417dddc878b32c3539ca33769c42c03b5 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 3 Jan 2023 11:03:56 +0530 Subject: [PATCH 156/334] Removed unnecessary additions in Ui state --- .../android/app/main/ManagerActivity.java | 65 +++++++++++-------- .../app/main/adapters/MegaNodeAdapter.java | 17 +++-- .../shares/incoming/IncomingSharesFragment.kt | 4 +- .../incoming/IncomingSharesViewModel.kt | 20 +----- .../incoming/model/IncomingSharesState.kt | 2 - .../shares/outgoing/OutgoingSharesFragment.kt | 4 +- .../outgoing/OutgoingSharesViewModel.kt | 23 ++----- .../outgoing/model/OutgoingSharesState.kt | 2 - 8 files changed, 60 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index b5e0001284a..210dad1a390 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -2597,6 +2597,9 @@ public void onPageScrollStateChanged(int state) { } else { Timber.d("Backup warning dialog is not show"); } + + addUnverifiedIncomingCountBadge(); + addUnverifiedOutgoingCountBadge(); } @@ -4245,32 +4248,6 @@ public void selectDrawerItemSharedItems() { tab.setIcon(R.drawable.link_ic); } }).attach(); - - ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { - if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { - TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); - if (incomingSharesTab != null) { - int incomingNodesCount = incomingSharesState.getUnVerifiedIncomingNodesCount(); - if (incomingNodesCount > 0) { - incomingSharesTab.getOrCreateBadge().setNumber(incomingNodesCount); - } - } - } - return Unit.INSTANCE; - }); - - ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { - if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { - TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); - if (outgoingSharesTab != null) { - int outgoingNodesCount = outgoingSharesState.getUnVerifiedOutGoingNodesCount(); - if (outgoingNodesCount > 0) { - outgoingSharesTab.getOrCreateBadge().setNumber(outgoingNodesCount); - } - } - } - return Unit.INSTANCE; - }); } updateSharesTab(); @@ -11432,4 +11409,40 @@ private void closeDrawer() { private boolean isBusinessAccount() { return megaApi.isBusinessAccount() && myAccountInfo.getAccountType() == BUSINESS; } + + /** + * Function to add unverified incoming count on tabs + */ + private void addUnverifiedIncomingCountBadge() { + ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { + if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { + TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); + if (incomingSharesTab != null) { + int incomingNodesCount = incomingSharesState.getUnVerifiedIncomingNodes().size(); + if (incomingNodesCount > 0) { + incomingSharesTab.getOrCreateBadge().setNumber(incomingNodesCount); + } + } + } + return Unit.INSTANCE; + }); + } + + /** + * Function to add unverified outgoing count on tabs + */ + private void addUnverifiedOutgoingCountBadge() { + ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { + if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { + TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); + if (outgoingSharesTab != null) { + int outgoingNodesCount = outgoingSharesState.getUnVerifiedOutGoingNodes().size(); + if (outgoingNodesCount > 0) { + outgoingSharesTab.getOrCreateBadge().setNumber(outgoingNodesCount); + } + } + } + return Unit.INSTANCE; + }); + } } diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index f4db93972e9..2a6fb89e355 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1091,9 +1091,11 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if(isMandatoryFingerprintVerificationNeeded && unverifiedIncomingNodes.contains(node.getHandle())) { - holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); - holder.permissionsIcon.setImageResource(R.drawable.serious_warning); + if (isMandatoryFingerprintVerificationNeeded) { + if (unverifiedIncomingNodes.get(position).getHandle() == node.getHandle()) { + holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); + holder.permissionsIcon.setImageResource(R.drawable.serious_warning); + } } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { @@ -1103,10 +1105,11 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else if (type == OUTGOING_SHARES_ADAPTER) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - - if(isMandatoryFingerprintVerificationNeeded && unverifiedOutgoingNodes.contains(node.getHandle())) { - holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); - holder.permissionsIcon.setImageResource(R.drawable.serious_warning); + if (isMandatoryFingerprintVerificationNeeded) { + if (unverifiedOutgoingNodes.get(position).getHandle() == node.getHandle()) { + holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); + holder.permissionsIcon.setImageResource(R.drawable.serious_warning); + } } } } else { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 95cdc2e39ac..bb6dd75f566 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -241,6 +241,8 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) + adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) + adapter?.setUnverifiedIncomingNodes(it.unVerifiedIncomingNodes) } } } @@ -276,14 +278,12 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { adapter?.parentHandle = state().incomingHandle adapter?.setListFragment(recyclerView) } - adapter?.setUnverifiedIncomingNodes(viewModel.getUnverifiedIncomingNodes()) if (managerActivity?.isList == false) gridLayoutManager?.spanSizeLookup = gridLayoutManager?.let { adapter?.getSpanSizeLookup(it.spanCount) } adapter?.isMultipleSelect = false recyclerView?.adapter = adapter - adapter?.setMandatoryFingerprintVerificationValue(viewModel.state.value.isMandatoryFingerprintVerificationNeeded) } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 0443f096c5b..f8d2dfbf6d1 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -75,22 +75,13 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() - getUnverifiedInComingShares(_state.value.sortOrder).forEach { shareData -> - if (!isInvalidHandle(shareData.nodeHandle)) { - getNodeByHandle(shareData.nodeHandle)?.let { megaNode -> - unverifiedIncomingNodes.add(megaNode) - } - } - } + val unverifiedIncomingNodes = getUnverifiedInComingShares(_state.value.sortOrder) + .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { it.copy(nodes = unverifiedIncomingNodes + _state.value.nodes) } } - viewModelScope.launch { - _state.update { - it.copy(unVerifiedIncomingNodesCount = unverifiedIncomingNodes.size) - } - } } @@ -223,9 +214,4 @@ class IncomingSharesViewModel @Inject constructor( it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) } } - - /** - * Get the unverified incoming nodes list to check in [MegaNodeAdapter] - */ - fun getUnverifiedIncomingNodes(): List = unverifiedIncomingNodes } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index d555602e62d..3baf88523a9 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -16,7 +16,6 @@ import nz.mega.sdk.MegaNode * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unVerifiedIncomingNodes List of unverified Incoming [MegaNode] - * @param unVerifiedIncomingNodesCount unVerifiedIncomingNodesCount to display on tab */ data class IncomingSharesState( val incomingHandle: Long = -1L, @@ -28,7 +27,6 @@ data class IncomingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unVerifiedIncomingNodes: List = emptyList(), - val unVerifiedIncomingNodesCount: Int = 0, ) { /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 09044f98574..6457ca645c4 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -234,7 +234,8 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) - + adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) + adapter?.setUnverifiedOutgoingNodes(it.unVerifiedOutGoingNodes) } } } @@ -270,7 +271,6 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { adapter?.parentHandle = state().outgoingHandle adapter?.setListFragment(recyclerView) } - adapter?.setUnverifiedOutgoingNodes(viewModel.getUnverifiedOutgoingNodes()) if (managerActivity?.isList == false) gridLayoutManager?.spanSizeLookup = gridLayoutManager?.spanCount?.let { adapter?.getSpanSizeLookup(it) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 75b1cfb284f..866aab619d8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -60,21 +60,11 @@ class OutgoingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() - getUnverifiedOutgoingShares(_state.value.sortOrder).forEach { shareData -> - if (!isInvalidHandle(shareData.nodeHandle)) { - getNodeByHandle(shareData.nodeHandle)?.let { megaNode -> - unverifiedOutgoingNodes.add(megaNode) - } - } - } + val unverifiedOutGoingNodes = getUnverifiedOutgoingShares(_state.value.sortOrder) + .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { - it.copy(nodes = unverifiedOutgoingNodes + _state.value.nodes) - } - } - - viewModelScope.launch { - _state.update { - it.copy(unVerifiedOutGoingNodesCount = unverifiedOutgoingNodes.size) + it.copy(nodes = unverifiedOutGoingNodes + _state.value.nodes) } } } @@ -208,9 +198,4 @@ class OutgoingSharesViewModel @Inject constructor( it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) } } - - /** - * Get the unverified outgoing nodes list to check in [MegaNodeAdapter] - */ - fun getUnverifiedOutgoingNodes(): List = unverifiedOutgoingNodes } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index 30a8fdf14dc..d692f78a7ce 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -16,7 +16,6 @@ import nz.mega.sdk.MegaNode * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unVerifiedOutGoingNodes List of Unverified outgoing [MegaNode] - * @param unVerifiedOutGoingNodesCount unVerifiedOutGoingNodesCount to display on tab */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -28,7 +27,6 @@ data class OutgoingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unVerifiedOutGoingNodes: List = emptyList(), - val unVerifiedOutGoingNodesCount: Int = 0, ) { /** From be9988f23bf367d3429da5c9a8a64d031cf4eaec Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 3 Jan 2023 11:08:39 +0530 Subject: [PATCH 157/334] empty check added to list --- .../privacy/android/app/main/adapters/MegaNodeAdapter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 2a6fb89e355..5f9dd1763ee 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1092,7 +1092,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } if (isMandatoryFingerprintVerificationNeeded) { - if (unverifiedIncomingNodes.get(position).getHandle() == node.getHandle()) { + if (!unverifiedIncomingNodes.isEmpty() && unverifiedIncomingNodes.get(position).getHandle() == node.getHandle()) { holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } @@ -1106,7 +1106,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); if (isMandatoryFingerprintVerificationNeeded) { - if (unverifiedOutgoingNodes.get(position).getHandle() == node.getHandle()) { + if (!unverifiedOutgoingNodes.isEmpty() && unverifiedOutgoingNodes.get(position).getHandle() == node.getHandle()) { holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } From a5344a4918b2ddbba491ecb44918a48d1bbee0ed Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 3 Jan 2023 14:45:49 +0530 Subject: [PATCH 158/334] Unit test updated --- .../incoming/IncomingSharesViewModelTest.kt | 15 ++++++++++----- .../outgoing/OutgoingSharesViewModelTest.kt | 7 ++++++- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 668af68b81f..6e899118fc5 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -63,7 +63,10 @@ class IncomingSharesViewModelTest { }.thenReturn(true) } - private val getUnverifiedInComingShares = mock() + private val getUnverifiedInComingShares = mock { + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) + } @Before fun setUp() { @@ -518,12 +521,14 @@ class IncomingSharesViewModelTest { } @Test - fun `test that unverified incoming shares are returned`() = runTest { - val shareData = ShareData("user", 8766L, 0, 987654678L, true) - whenever(getUnverifiedInComingShares(underTest.state.value.sortOrder)) - .thenReturn(listOf(shareData)) + fun `test that unverified outgoing shares are returned`() = runTest { val node1 = mock() whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() + initViewModel() + underTest.state.map { it.nodes }.distinctUntilChanged() + .test { + assertThat(awaitItem().size).isEqualTo(1) + } } } \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index 88753e78df5..a7f98d26b2f 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -60,7 +60,7 @@ class OutgoingSharesViewModelTest { }.thenReturn(true) } - private val getUnverifiedOutgoingShares = mock() { + private val getUnverifiedOutgoingShares = mock { val shareData = ShareData("user", 8766L, 0, 987654678L, true) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } @@ -449,5 +449,10 @@ class OutgoingSharesViewModelTest { val node1 = mock() whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() + initViewModel() + underTest.state.map { it.nodes }.distinctUntilChanged() + .test { + assertThat(awaitItem().size).isEqualTo(1) + } } } From c770d4c1768ce0d31a9f12307b19886684a7fa2f Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 3 Jan 2023 15:43:45 +0530 Subject: [PATCH 159/334] Code review comments resolved Unit test cases modified accordingly --- .../java/mega/privacy/android/app/main/ManagerActivity.java | 2 +- .../presentation/shares/incoming/IncomingSharesFragment.kt | 3 +-- .../presentation/shares/incoming/IncomingSharesViewModel.kt | 4 +--- .../presentation/shares/outgoing/OutgoingSharesFragment.kt | 4 ++-- .../presentation/shares/outgoing/OutgoingSharesViewModel.kt | 6 ++---- .../shares/outgoing/model/OutgoingSharesState.kt | 4 ++-- .../shares/incoming/IncomingSharesViewModelTest.kt | 2 +- .../shares/outgoing/OutgoingSharesViewModelTest.kt | 2 +- 8 files changed, 11 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 210dad1a390..75ae291523c 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -11436,7 +11436,7 @@ private void addUnverifiedOutgoingCountBadge() { if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); if (outgoingSharesTab != null) { - int outgoingNodesCount = outgoingSharesState.getUnVerifiedOutGoingNodes().size(); + int outgoingNodesCount = outgoingSharesState.getUnVerifiedOutgoingNodes().size(); if (outgoingNodesCount > 0) { outgoingSharesTab.getOrCreateBadge().setNumber(outgoingNodesCount); } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index bb6dd75f566..9d62608e24c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -230,8 +230,6 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { hideTabs(true) return@collect } - - updateNodes(it.nodes) hideTabs(!it.isFirstNavigationLevel()) managerActivity?.showFabButton() @@ -243,6 +241,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) adapter?.setUnverifiedIncomingNodes(it.unVerifiedIncomingNodes) + updateNodes(it.unVerifiedIncomingNodes + it.nodes) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index f8d2dfbf6d1..bd4d2f27092 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -49,8 +49,6 @@ class IncomingSharesViewModel @Inject constructor( /** stack of scroll position for each depth */ private val lastPositionStack: Stack = Stack() - private val unverifiedIncomingNodes = mutableListOf() - init { viewModelScope.launch { refreshNodes()?.let { setNodes(it) } @@ -79,7 +77,7 @@ class IncomingSharesViewModel @Inject constructor( .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { - it.copy(nodes = unverifiedIncomingNodes + _state.value.nodes) + it.copy(unVerifiedIncomingNodes = unverifiedIncomingNodes) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 6457ca645c4..3ab104c61fc 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -224,7 +224,6 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { return@collect } - updateNodes(it.nodes) hideTabs(!it.isFirstNavigationLevel()) managerActivity?.showFabButton() @@ -235,7 +234,8 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { hideActionMode() setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) - adapter?.setUnverifiedOutgoingNodes(it.unVerifiedOutGoingNodes) + adapter?.setUnverifiedOutgoingNodes(it.unVerifiedOutgoingNodes) + updateNodes(it.unVerifiedOutgoingNodes + it.nodes) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 866aab619d8..bc76d911b88 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -47,8 +47,6 @@ class OutgoingSharesViewModel @Inject constructor( /** stack of scroll position for each depth */ private val lastPositionStack: Stack = Stack() - private val unverifiedOutgoingNodes = mutableListOf() - init { viewModelScope.launch { refreshNodes()?.let { setNodes(it) } @@ -60,11 +58,11 @@ class OutgoingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() - val unverifiedOutGoingNodes = getUnverifiedOutgoingShares(_state.value.sortOrder) + val unverifiedOutgoingNodes = getUnverifiedOutgoingShares(_state.value.sortOrder) .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { - it.copy(nodes = unverifiedOutGoingNodes + _state.value.nodes) + it.copy(unVerifiedOutgoingNodes = unverifiedOutgoingNodes) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index d692f78a7ce..b9b121326e1 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -15,7 +15,7 @@ import nz.mega.sdk.MegaNode * @param isLoading true if the nodes are loading * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed - * @param unVerifiedOutGoingNodes List of Unverified outgoing [MegaNode] + * @param unVerifiedOutgoingNodes List of Unverified outgoing [MegaNode] */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -26,7 +26,7 @@ data class OutgoingSharesState( val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, - val unVerifiedOutGoingNodes: List = emptyList(), + val unVerifiedOutgoingNodes: List = emptyList(), ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 6e899118fc5..07faa706a77 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -526,7 +526,7 @@ class IncomingSharesViewModelTest { whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() initViewModel() - underTest.state.map { it.nodes }.distinctUntilChanged() + underTest.state.map { it.unVerifiedIncomingNodes }.distinctUntilChanged() .test { assertThat(awaitItem().size).isEqualTo(1) } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index a7f98d26b2f..f7bfb4d5a09 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -450,7 +450,7 @@ class OutgoingSharesViewModelTest { whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() initViewModel() - underTest.state.map { it.nodes }.distinctUntilChanged() + underTest.state.map { it.unVerifiedOutgoingNodes }.distinctUntilChanged() .test { assertThat(awaitItem().size).isEqualTo(1) } From d2a25fd21c232ff852d57208fd3608ed44065ac8 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 3 Jan 2023 18:04:44 +0530 Subject: [PATCH 160/334] Unverified node handle checked with adapter current node & UI items added accordingly --- .../app/main/adapters/MegaNodeAdapter.java | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 5f9dd1763ee..b67b3ec855f 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -92,10 +92,8 @@ import mega.privacy.android.app.presentation.rubbishbin.RubbishBinFragment; import mega.privacy.android.app.presentation.search.SearchFragment; import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesFragment; -import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel; import mega.privacy.android.app.presentation.shares.links.LinksFragment; import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesFragment; -import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel; import mega.privacy.android.app.utils.ColorUtils; import mega.privacy.android.app.utils.MegaNodeUtil; import mega.privacy.android.app.utils.NodeTakenDownDialogListener; @@ -1091,11 +1089,8 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if (isMandatoryFingerprintVerificationNeeded) { - if (!unverifiedIncomingNodes.isEmpty() && unverifiedIncomingNodes.get(position).getHandle() == node.getHandle()) { - holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); - holder.permissionsIcon.setImageResource(R.drawable.serious_warning); - } + if (isMandatoryFingerprintVerificationNeeded && !unverifiedIncomingNodes.isEmpty()) { + showUnverifiedNodeUi(unverifiedIncomingNodes, node, holder); } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { @@ -1105,11 +1100,8 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else if (type == OUTGOING_SHARES_ADAPTER) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - if (isMandatoryFingerprintVerificationNeeded) { - if (!unverifiedOutgoingNodes.isEmpty() && unverifiedOutgoingNodes.get(position).getHandle() == node.getHandle()) { - holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); - holder.permissionsIcon.setImageResource(R.drawable.serious_warning); - } + if (isMandatoryFingerprintVerificationNeeded && !unverifiedOutgoingNodes.isEmpty()) { + showUnverifiedNodeUi(unverifiedOutgoingNodes, node, holder); } } } else { @@ -1533,4 +1525,21 @@ public void setUnverifiedIncomingNodes(List nodes) { public void setUnverifiedOutgoingNodes(List nodes) { this.unverifiedOutgoingNodes = nodes; } + + /** + * Function to check if current node is unverified & show Ui items accordingly + * + * @param unverifiedNodes Unverified Nodes List + * @param adapterNode Current node from adapter + * @param holder [ViewHolderBrowserList] + */ + private void showUnverifiedNodeUi(List unverifiedNodes, MegaNode adapterNode, ViewHolderBrowserList holder) { + for (int i = 0; i < unverifiedNodes.size(); i++) { + if (unverifiedNodes.get(i).getHandle() == adapterNode.getHandle()) { + holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); + holder.permissionsIcon.setImageResource(R.drawable.serious_warning); + break; + } + } + } } From 2a27e74fbe56c4c5f941fa4ce5b41493496c04d4 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 3 Jan 2023 18:54:08 +0530 Subject: [PATCH 161/334] variable name modified --- .../privacy/android/app/main/adapters/MegaNodeAdapter.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index b67b3ec855f..f2473217d7f 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1530,12 +1530,12 @@ public void setUnverifiedOutgoingNodes(List nodes) { * Function to check if current node is unverified & show Ui items accordingly * * @param unverifiedNodes Unverified Nodes List - * @param adapterNode Current node from adapter + * @param currentNode Current node from adapter * @param holder [ViewHolderBrowserList] */ - private void showUnverifiedNodeUi(List unverifiedNodes, MegaNode adapterNode, ViewHolderBrowserList holder) { + private void showUnverifiedNodeUi(List unverifiedNodes, MegaNode currentNode, ViewHolderBrowserList holder) { for (int i = 0; i < unverifiedNodes.size(); i++) { - if (unverifiedNodes.get(i).getHandle() == adapterNode.getHandle()) { + if (unverifiedNodes.get(i).getHandle() == currentNode.getHandle()) { holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); break; From ae1f2067fc9c18eeb27461d5d897d3fdc0a77df3 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 4 Jan 2023 09:33:34 +0530 Subject: [PATCH 162/334] Code optimised --- .../android/app/main/ManagerActivity.java | 51 +++++++++---------- .../app/main/adapters/MegaNodeAdapter.java | 40 +++++++++------ 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 75ae291523c..6d83e2f353e 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -2597,9 +2597,20 @@ public void onPageScrollStateChanged(int state) { } else { Timber.d("Backup warning dialog is not show"); } + ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { + if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { + addUnverifiedIncomingCountBadge(incomingSharesState.getUnVerifiedIncomingNodes().size()); + } + return Unit.INSTANCE; + }); + + ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { + if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { + addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnVerifiedOutgoingNodes().size()); + } + return Unit.INSTANCE; + }); - addUnverifiedIncomingCountBadge(); - addUnverifiedOutgoingCountBadge(); } @@ -11413,36 +11424,24 @@ private boolean isBusinessAccount() { /** * Function to add unverified incoming count on tabs */ - private void addUnverifiedIncomingCountBadge() { - ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { - if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { - TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); - if (incomingSharesTab != null) { - int incomingNodesCount = incomingSharesState.getUnVerifiedIncomingNodes().size(); - if (incomingNodesCount > 0) { - incomingSharesTab.getOrCreateBadge().setNumber(incomingNodesCount); - } - } + private void addUnverifiedIncomingCountBadge(int unverifiedNodesCount) { + TabLayout.Tab incomingSharesTab = tabLayoutShares.getTabAt(0); + if (incomingSharesTab != null) { + if (unverifiedNodesCount > 0) { + incomingSharesTab.getOrCreateBadge().setNumber(unverifiedNodesCount); } - return Unit.INSTANCE; - }); + } } /** * Function to add unverified outgoing count on tabs */ - private void addUnverifiedOutgoingCountBadge() { - ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { - if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { - TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); - if (outgoingSharesTab != null) { - int outgoingNodesCount = outgoingSharesState.getUnVerifiedOutgoingNodes().size(); - if (outgoingNodesCount > 0) { - outgoingSharesTab.getOrCreateBadge().setNumber(outgoingNodesCount); - } - } + private void addUnverifiedOutgoingCountBadge(int unverifiedNodesCount) { + TabLayout.Tab outgoingSharesTab = tabLayoutShares.getTabAt(1); + if (outgoingSharesTab != null) { + if (unverifiedNodesCount > 0) { + outgoingSharesTab.getOrCreateBadge().setNumber(unverifiedNodesCount); } - return Unit.INSTANCE; - }); + } } } diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index f2473217d7f..bbddf9a4917 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -69,6 +69,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Set; import mega.privacy.android.app.MegaApplication; import mega.privacy.android.app.MimeTypeList; @@ -143,8 +144,8 @@ public class MegaNodeAdapter extends RecyclerView.Adapter unverifiedIncomingNodes; - private List unverifiedOutgoingNodes; + private Set unverifiedIncomingNodeHandles; + private Set unverifiedOutgoingNodeHandles; private Boolean isMandatoryFingerprintVerificationNeeded; public static class ViewHolderBrowser extends RecyclerView.ViewHolder { @@ -1089,8 +1090,8 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if (isMandatoryFingerprintVerificationNeeded && !unverifiedIncomingNodes.isEmpty()) { - showUnverifiedNodeUi(unverifiedIncomingNodes, node, holder); + if (isMandatoryFingerprintVerificationNeeded && !unverifiedIncomingNodeHandles.isEmpty()) { + showUnverifiedNodeUi(unverifiedIncomingNodeHandles, node, holder); } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { @@ -1100,8 +1101,8 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else if (type == OUTGOING_SHARES_ADAPTER) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - if (isMandatoryFingerprintVerificationNeeded && !unverifiedOutgoingNodes.isEmpty()) { - showUnverifiedNodeUi(unverifiedOutgoingNodes, node, holder); + if (isMandatoryFingerprintVerificationNeeded && !unverifiedOutgoingNodeHandles.isEmpty()) { + showUnverifiedNodeUi(unverifiedOutgoingNodeHandles, node, holder); } } } else { @@ -1518,12 +1519,24 @@ public void setMandatoryFingerprintVerificationValue(boolean isVerificationNeede this.isMandatoryFingerprintVerificationNeeded = isVerificationNeeded; } + /** + * Adds unverified incoming nodes to Set + * + * @param nodes - List of incoming [MegaNode] + */ public void setUnverifiedIncomingNodes(List nodes) { - this.unverifiedIncomingNodes = nodes; + unverifiedIncomingNodeHandles.clear(); + nodes.forEach(megaNode -> unverifiedIncomingNodeHandles.add(megaNode.getHandle())); } + /** + * Adds unverified outgoing nodes to Set + * + * @param nodes - List of outgoing [MegaNode] + */ public void setUnverifiedOutgoingNodes(List nodes) { - this.unverifiedOutgoingNodes = nodes; + unverifiedOutgoingNodeHandles.clear(); + nodes.forEach(megaNode -> unverifiedOutgoingNodeHandles.add(megaNode.getHandle())); } /** @@ -1533,13 +1546,10 @@ public void setUnverifiedOutgoingNodes(List nodes) { * @param currentNode Current node from adapter * @param holder [ViewHolderBrowserList] */ - private void showUnverifiedNodeUi(List unverifiedNodes, MegaNode currentNode, ViewHolderBrowserList holder) { - for (int i = 0; i < unverifiedNodes.size(); i++) { - if (unverifiedNodes.get(i).getHandle() == currentNode.getHandle()) { - holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); - holder.permissionsIcon.setImageResource(R.drawable.serious_warning); - break; - } + private void showUnverifiedNodeUi(Set unverifiedNodes, MegaNode currentNode, ViewHolderBrowserList holder) { + if (unverifiedNodes.contains(currentNode.getHandle())) { + holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); + holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } } } From ad89306decb6d1caf68b235813cdc87a63962fad Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 4 Jan 2023 20:32:30 +0530 Subject: [PATCH 163/334] AND-15344 - Called openShareDialog from megaApi on click of share folder option. Minor mistake rectified in MegaApiFacade --- .../NodeOptionsBottomSheetDialogFragment.java | 36 ++++++++++++------- .../android/data/facade/MegaApiFacade.kt | 2 +- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 08b2d7d1dfd..57d5ab8926d 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -83,6 +83,7 @@ import mega.privacy.android.app.activities.WebViewActivity; import mega.privacy.android.app.imageviewer.ImageViewerActivity; import mega.privacy.android.app.interfaces.SnackbarShower; +import mega.privacy.android.app.listeners.OptionalMegaRequestListenerInterface; import mega.privacy.android.app.main.DrawerItem; import mega.privacy.android.app.main.FileContactListActivity; import mega.privacy.android.app.presentation.fileinfo.FileInfoActivity; @@ -98,7 +99,10 @@ import mega.privacy.android.app.utils.StringResourcesUtils; import mega.privacy.android.app.utils.ViewUtils; import mega.privacy.android.domain.entity.SortOrder; +import nz.mega.sdk.MegaApiJava; +import nz.mega.sdk.MegaError; import nz.mega.sdk.MegaNode; +import nz.mega.sdk.MegaRequest; import nz.mega.sdk.MegaShare; import nz.mega.sdk.MegaUser; import timber.log.Timber; @@ -954,19 +958,27 @@ public void onClick(View v) { break; case R.id.share_folder_option: - nodeType = checkBackupNodeTypeByHandle(megaApi, node); - if (isOutShare(node)) { - i = new Intent(requireContext(), FileContactListActivity.class); - i.putExtra(NAME, node.getHandle()); - startActivity(i); - } else { - if (nodeType != BACKUP_NONE) { - ((ManagerActivity) requireActivity()).showWarningDialogOfShare(node, nodeType, ACTION_BACKUP_SHARE_FOLDER); - } else { - nC.selectContactToShareFolder(node); + megaApi.openShareDialog(node, new OptionalMegaRequestListenerInterface() { + @Override + public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { + super.onRequestFinish(api, request, error); + if (error.getErrorCode() == MegaError.API_OK) { + int nodeType = checkBackupNodeTypeByHandle(megaApi, node); + if (isOutShare(node)) { + Intent intent = new Intent(requireContext(), FileContactListActivity.class); + intent.putExtra(NAME, node.getHandle()); + startActivity(intent); + } else { + if (nodeType != BACKUP_NONE) { + ((ManagerActivity) requireActivity()).showWarningDialogOfShare(node, nodeType, ACTION_BACKUP_SHARE_FOLDER); + } else { + nC.selectContactToShareFolder(node); + } + } + dismissAllowingStateLoss(); + } } - } - dismissAllowingStateLoss(); + }); break; case R.id.clear_share_option: diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index d25712811dd..bbec3d70c50 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -876,7 +876,7 @@ internal class MegaApiFacade @Inject constructor( megaApi.getUnverifiedIncomingShares(order) override suspend fun getUnverifiedOutgoingShares(order: Int): List = - megaApi.getUnverifiedIncomingShares(order) + megaApi.getUnverifiedOutgoingShares(order) override fun openShareDialog( megaNode: MegaNode, From e92d785efa6fb588496328ee6fba6bbfd4d8761b Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 4 Jan 2023 22:11:36 +0530 Subject: [PATCH 164/334] variable name of getUnverifiedInComingShares changed to getUnverifiedIncomingShares --- .../NodeOptionsBottomSheetDialogFragment.java | 171 ++++++++++-------- .../presentation/manager/ManagerViewModel.kt | 7 +- .../presentation/search/SearchViewModel.kt | 12 +- .../presentation/search/model/SearchState.kt | 2 + .../incoming/IncomingSharesViewModel.kt | 4 +- .../manager/ManagerViewModelTest.kt | 15 +- .../incoming/IncomingSharesViewModelTest.kt | 4 +- .../data/repository/MegaNodeRepository.kt | 2 +- 8 files changed, 120 insertions(+), 97 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 57d5ab8926d..114a1c936e9 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -67,6 +67,7 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.core.content.res.ResourcesCompat; +import androidx.lifecycle.Lifecycle; import androidx.lifecycle.ViewModelProvider; import com.google.android.material.switchmaterial.SwitchMaterial; @@ -77,10 +78,12 @@ import java.util.Collections; import java.util.List; +import kotlin.Unit; import mega.privacy.android.app.MegaOffline; import mega.privacy.android.app.MimeTypeList; import mega.privacy.android.app.R; import mega.privacy.android.app.activities.WebViewActivity; +import mega.privacy.android.app.arch.extensions.ViewExtensionsKt; import mega.privacy.android.app.imageviewer.ImageViewerActivity; import mega.privacy.android.app.interfaces.SnackbarShower; import mega.privacy.android.app.listeners.OptionalMegaRequestListenerInterface; @@ -328,80 +331,81 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } if (isOnline(requireContext())) { - if(searchViewModel.getMandatoryFingerPrintVerificationState().getValue()) { - ////TODO This flag for false for now. This will get manipulated after SDK changes - showOwnerSharedFolder(); - TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); - optionVerifyUser.setText(StringResourcesUtils.getString(R.string.shared_items_bottom_sheet_menu_verify_user, getMegaUserNameDB(user))); - nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); - optionVerifyUser.setVisibility(View.VISIBLE); - optionVerifyUser.setOnClickListener(this); - - //Removing the click listener & making it View.GONE - optionDownload.setOnClickListener(null); - optionDownload.setVisibility(View.GONE); - - //Removing the click listener & making it View.GONE - optionOffline.setOnClickListener(null); - optionOffline.setVisibility(View.GONE); - - separatorDownload.setVisibility(View.GONE); - separatorLabel.setVisibility(View.GONE); - separatorOpen.setVisibility(View.GONE); - separatorModify.setVisibility(View.GONE); - separatorShares.setVisibility(View.GONE); - - //Removing the click listener & making it View.GONE - optionSendChat.setOnClickListener(null); - optionSendChat.setVisibility(View.GONE); - - //Removing the click listener & making it View.GONE - optionCopy.setOnClickListener(null); - optionCopy.setVisibility(View.GONE); - - } else { - nodeName.setText(node.getName()); - if (node.isFolder()) { - optionVersionsLayout.setVisibility(View.GONE); - nodeInfo.setText(getMegaNodeFolderInfo(node)); - nodeVersionsIcon.setVisibility(View.GONE); + ViewExtensionsKt.collectFlow(requireActivity(), searchViewModel.getState(), Lifecycle.State.STARTED, state -> { + if (state.isMandatoryFingerPrintVerificationRequired()) { + showOwnerSharedFolder(); + TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); + optionVerifyUser.setText(StringResourcesUtils.getString(R.string.shared_items_bottom_sheet_menu_verify_user, getMegaUserNameDB(user))); + nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + optionVerifyUser.setVisibility(View.VISIBLE); + optionVerifyUser.setOnClickListener(this); + + //Removing the click listener & making it View.GONE + optionDownload.setOnClickListener(null); + optionDownload.setVisibility(View.GONE); - nodeThumb.setImageResource(getFolderIcon(node, drawerItem)); + //Removing the click listener & making it View.GONE + optionOffline.setOnClickListener(null); + optionOffline.setVisibility(View.GONE); - if (isEmptyFolder(node)) { - counterSave--; - optionOffline.setVisibility(View.GONE); - } + separatorDownload.setVisibility(View.GONE); + separatorLabel.setVisibility(View.GONE); + separatorOpen.setVisibility(View.GONE); + separatorModify.setVisibility(View.GONE); + separatorShares.setVisibility(View.GONE); - counterShares--; + //Removing the click listener & making it View.GONE + optionSendChat.setOnClickListener(null); optionSendChat.setVisibility(View.GONE); - } else { - if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize()) - && accessLevel >= MegaShare.ACCESS_READWRITE) { - optionEdit.setVisibility(View.VISIBLE); - } - - nodeInfo.setText(getFileInfo(node)); - if (megaApi.hasVersions(node)) { - nodeVersionsIcon.setVisibility(View.VISIBLE); - optionVersionsLayout.setVisibility(View.VISIBLE); - versions.setText(String.valueOf(megaApi.getNumVersions(node))); - } else { - nodeVersionsIcon.setVisibility(View.GONE); + //Removing the click listener & making it View.GONE + optionCopy.setOnClickListener(null); + optionCopy.setVisibility(View.GONE); + } else { + nodeName.setText(node.getName()); + if (node.isFolder()) { optionVersionsLayout.setVisibility(View.GONE); - } + nodeInfo.setText(getMegaNodeFolderInfo(node)); + nodeVersionsIcon.setVisibility(View.GONE); - setNodeThumbnail(requireContext(), node, nodeThumb); + nodeThumb.setImageResource(getFolderIcon(node, drawerItem)); + + if (isEmptyFolder(node)) { + counterSave--; + optionOffline.setVisibility(View.GONE); + } - if (isTakenDown) { counterShares--; optionSendChat.setVisibility(View.GONE); } else { - optionSendChat.setVisibility(View.VISIBLE); + if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize()) + && accessLevel >= MegaShare.ACCESS_READWRITE) { + optionEdit.setVisibility(View.VISIBLE); + } + + nodeInfo.setText(getFileInfo(node)); + + if (megaApi.hasVersions(node)) { + nodeVersionsIcon.setVisibility(View.VISIBLE); + optionVersionsLayout.setVisibility(View.VISIBLE); + versions.setText(String.valueOf(megaApi.getNumVersions(node))); + } else { + nodeVersionsIcon.setVisibility(View.GONE); + optionVersionsLayout.setVisibility(View.GONE); + } + + setNodeThumbnail(requireContext(), node, nodeThumb); + + if (isTakenDown) { + counterShares--; + optionSendChat.setVisibility(View.GONE); + } else { + optionSendChat.setVisibility(View.VISIBLE); + } } } - } + return Unit.INSTANCE; + }); } if (isTakenDown) { @@ -958,26 +962,21 @@ public void onClick(View v) { break; case R.id.share_folder_option: - megaApi.openShareDialog(node, new OptionalMegaRequestListenerInterface() { - @Override - public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { - super.onRequestFinish(api, request, error); - if (error.getErrorCode() == MegaError.API_OK) { - int nodeType = checkBackupNodeTypeByHandle(megaApi, node); - if (isOutShare(node)) { - Intent intent = new Intent(requireContext(), FileContactListActivity.class); - intent.putExtra(NAME, node.getHandle()); - startActivity(intent); - } else { - if (nodeType != BACKUP_NONE) { - ((ManagerActivity) requireActivity()).showWarningDialogOfShare(node, nodeType, ACTION_BACKUP_SHARE_FOLDER); - } else { - nC.selectContactToShareFolder(node); + ViewExtensionsKt.collectFlow(requireActivity(), searchViewModel.getState(), Lifecycle.State.STARTED, state -> { + if (state.isMandatoryFingerPrintVerificationRequired()) { + megaApi.openShareDialog(node, new OptionalMegaRequestListenerInterface() { + @Override + public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { + super.onRequestFinish(api, request, error); + if (error.getErrorCode() == MegaError.API_OK) { + showShareFolderOptions(); } } - dismissAllowingStateLoss(); - } + }); + } else { + showShareFolderOptions(); } + return Unit.INSTANCE; }); break; @@ -1205,4 +1204,20 @@ private int getAdapterType() { return INVALID_VALUE; } } + + private void showShareFolderOptions() { + int nodeType = checkBackupNodeTypeByHandle(megaApi, node); + if (isOutShare(node)) { + Intent intent = new Intent(requireContext(), FileContactListActivity.class); + intent.putExtra(NAME, node.getHandle()); + startActivity(intent); + } else { + if (nodeType != BACKUP_NONE) { + ((ManagerActivity) requireActivity()).showWarningDialogOfShare(node, nodeType, ACTION_BACKUP_SHARE_FOLDER); + } else { + nC.selectContactToShareFolder(node); + } + } + dismissAllowingStateLoss(); + } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index 6326ca2494b..c52914206af 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -24,6 +24,7 @@ import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates +import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.fragments.homepage.Event import mega.privacy.android.app.presentation.extensions.getState import mega.privacy.android.app.presentation.extensions.getStateFlow @@ -44,6 +45,7 @@ import mega.privacy.android.domain.usecase.BroadcastUploadPauseState import mega.privacy.android.domain.usecase.CheckCameraUpload import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetExtendedAccountDetail +import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetFullAccountInfo import mega.privacy.android.domain.usecase.GetNumUnreadUserAlerts import mega.privacy.android.domain.usecase.GetPricing @@ -107,7 +109,8 @@ class ManagerViewModel @Inject constructor( private val getPricing: GetPricing, private val getFullAccountInfo: GetFullAccountInfo, private val getActiveSubscription: GetActiveSubscription, - private val getUnverifiedInComingShares: GetUnverifiedIncomingShares, + private val getFeatureFlagValue: GetFeatureFlagValue, + private val getUnverifiedIncomingShares: GetUnverifiedIncomingShares, private val getUnverifiedOutgoingShares: GetUnverifiedOutgoingShares, ) : ViewModel() { @@ -165,7 +168,7 @@ class ManagerViewModel @Inject constructor( } viewModelScope.launch { - val incomingShares = getUnverifiedInComingShares(SortOrder.ORDER_DEFAULT_ASC).size + val incomingShares = getUnverifiedIncomingShares(SortOrder.ORDER_DEFAULT_ASC).size val outgoingShares = getUnverifiedOutgoingShares(SortOrder.ORDER_DEFAULT_ASC).size _state.update { it.copy(pendingActionsCount = incomingShares + outgoingShares) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt index f22ccdd347c..b8c7b5cea02 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt @@ -68,14 +68,6 @@ class SearchViewModel @Inject constructor( */ val stateLiveData = _state.map { Event(it) }.asLiveData() - private val _mandatoryFingerPrintVerificationState = MutableStateFlow(false) - - /** - * State for [MandatoryFingerPrintVerification] feature flag value - */ - val mandatoryFingerPrintVerificationState: StateFlow = - _mandatoryFingerPrintVerificationState - /** * Monitor global node updates */ @@ -344,8 +336,8 @@ class SearchViewModel @Inject constructor( */ fun isMandatoryFingerprintRequired() { viewModelScope.launch { - _mandatoryFingerPrintVerificationState.update { - getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification) + _state.update { + it.copy(isMandatoryFingerPrintVerificationRequired = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/model/SearchState.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/model/SearchState.kt index da0bf5bd659..153b6407b5e 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/model/SearchState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/model/SearchState.kt @@ -17,6 +17,7 @@ import nz.mega.sdk.MegaNode * @param searchQuery current search query * @param searchDepth current search depth count * @param isInProgress current progress state of the search request + * @param isMandatoryFingerPrintVerificationRequired - isMandatoryFingerPrintVerificationRequired */ data class SearchState( val nodes: List?, @@ -27,4 +28,5 @@ data class SearchState( val searchQuery: String?, val searchDepth: Int, val isInProgress: Boolean, + val isMandatoryFingerPrintVerificationRequired: Boolean = false, ) \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index bd4d2f27092..37d6e7b0e2b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -37,7 +37,7 @@ class IncomingSharesViewModel @Inject constructor( private val getOthersSortOrder: GetOthersSortOrder, monitorNodeUpdates: MonitorNodeUpdates, private val getFeatureFlagValue: GetFeatureFlagValue, - private val getUnverifiedInComingShares: GetUnverifiedIncomingShares, + private val getUnverifiedIncomingShares: GetUnverifiedIncomingShares, ) : ViewModel() { /** private UI state */ @@ -73,7 +73,7 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() - val unverifiedIncomingNodes = getUnverifiedInComingShares(_state.value.sortOrder) + val unverifiedIncomingNodes = getUnverifiedIncomingShares(_state.value.sortOrder) .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index f31c5bcc527..a7e4a9f90d2 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -21,6 +21,7 @@ import mega.privacy.android.app.domain.usecase.GetPrimarySyncHandle import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates +import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.manager.ManagerViewModel import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.manager.model.TransfersTab @@ -32,6 +33,7 @@ import mega.privacy.android.domain.entity.contacts.ContactRequestStatus import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.usecase.CheckCameraUpload import mega.privacy.android.domain.usecase.GetCloudSortOrder +import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetNumUnreadUserAlerts import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares @@ -72,11 +74,19 @@ class ManagerViewModelTest { private val checkCameraUpload = mock() private val getCloudSortOrder = mock() private val monitorConnectivity = mock() + + private val getFeatureFlagValue = + mock { + onBlocking { + invoke(AppFeatures.MandatoryFingerprintVerification) + }.thenReturn(true) + } + private val getUnverifiedOutgoingShares = mock { val shareData = ShareData("user", 8766L, 0, 987654678L, true) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } - private val getUnverifiedInComingShares = mock { + private val getUnverifiedIncomingShares = mock { val shareData = ShareData("user", 8766L, 0, 987654678L, true) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } @@ -118,7 +128,8 @@ class ManagerViewModelTest { getPricing = mock(), getFullAccountInfo = mock(), getActiveSubscription = mock(), - getUnverifiedInComingShares = getUnverifiedInComingShares, + getFeatureFlagValue = getFeatureFlagValue, + getUnverifiedIncomingShares = getUnverifiedIncomingShares, getUnverifiedOutgoingShares = getUnverifiedOutgoingShares, ) } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 07faa706a77..493fd0719e1 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -63,7 +63,7 @@ class IncomingSharesViewModelTest { }.thenReturn(true) } - private val getUnverifiedInComingShares = mock { + private val getUnverifiedIncomingShares = mock { val shareData = ShareData("user", 8766L, 0, 987654678L, true) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } @@ -84,7 +84,7 @@ class IncomingSharesViewModelTest { getOtherSortOrder, monitorNodeUpdates, getFeatureFlagValue, - getUnverifiedInComingShares, + getUnverifiedIncomingShares, ) } diff --git a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt index 93e10042a6d..06e25782ead 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt @@ -239,7 +239,7 @@ interface MegaNodeRepository { * * @return List of [ShareData] */ - suspend fun getUnVerifiedInComingShares(order: SortOrder): List + suspend fun getUnverifiedIncomingShares(order: SortOrder): List /** * Provides Unverified outgoing shares count from SDK From ed31a19ce85cd9822c8fc1430c24b59a790a6ad5 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 5 Jan 2023 18:54:16 +0530 Subject: [PATCH 165/334] AND-15324 Security upgrade dialog placeholder icon replaced with expected icon --- .../SecurityUpgradeDialogView.kt | 2 +- .../main/res/drawable/ic_security_upgrade.xml | 102 ++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_security_upgrade.xml diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt index 06956ba3a26..4aebdfd3534 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt @@ -59,7 +59,7 @@ fun SecurityUpgradeDialogView( .height(140.dp) .width(114.dp) .testTag("HeaderImage"), - imageVector = ImageVector.vectorResource(id = R.drawable.ic_contact_verification_required), + imageVector = ImageVector.vectorResource(id = R.drawable.ic_security_upgrade), contentDescription = "Empty") Text(text = stringResource(id = R.string.shared_items_security_upgrade_dialog_title), diff --git a/app/src/main/res/drawable/ic_security_upgrade.xml b/app/src/main/res/drawable/ic_security_upgrade.xml new file mode 100644 index 00000000000..753190ec65e --- /dev/null +++ b/app/src/main/res/drawable/ic_security_upgrade.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 77572dc6e8c33cc8f519c8a844c4ca1ee2a1590b Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 5 Jan 2023 19:09:15 +0530 Subject: [PATCH 166/334] Added content description --- .../presentation/fingerprintauth/SecurityUpgradeDialogView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt index 4aebdfd3534..96e9afe03cb 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt @@ -60,7 +60,7 @@ fun SecurityUpgradeDialogView( .width(114.dp) .testTag("HeaderImage"), imageVector = ImageVector.vectorResource(id = R.drawable.ic_security_upgrade), - contentDescription = "Empty") + contentDescription = "Security Upgrade Icon") Text(text = stringResource(id = R.string.shared_items_security_upgrade_dialog_title), style = subtitle1, From 1257adba034bcb2d305cb879548ac45d83c020b4 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 5 Jan 2023 18:33:07 +0530 Subject: [PATCH 167/334] Security upgrade dialog displayed after receiving EVENT_UPGRADE_SECURITY --- .../android/app/main/ManagerActivity.java | 9 ++++++ .../presentation/manager/ManagerViewModel.kt | 30 ++++++++++++++++++- .../manager/model/ManagerState.kt | 10 ++++++- 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 6d83e2f353e..99987bc4c14 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -355,6 +355,7 @@ import mega.privacy.android.app.objects.PasscodeManagement; import mega.privacy.android.app.presentation.clouddrive.FileBrowserFragment; import mega.privacy.android.app.presentation.clouddrive.FileBrowserViewModel; +import mega.privacy.android.app.presentation.fingerprintauth.SecurityUpgradeDialogFragment; import mega.privacy.android.app.presentation.inbox.InboxFragment; import mega.privacy.android.app.presentation.inbox.InboxViewModel; import mega.privacy.android.app.presentation.manager.ManagerViewModel; @@ -451,6 +452,7 @@ import nz.mega.sdk.MegaChatRoom; import nz.mega.sdk.MegaContactRequest; import nz.mega.sdk.MegaError; +import nz.mega.sdk.MegaEvent; import nz.mega.sdk.MegaFolderInfo; import nz.mega.sdk.MegaNode; import nz.mega.sdk.MegaRequest; @@ -1424,6 +1426,13 @@ protected void onCreate(Bundle savedInstanceState) { return null; })); + ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, state -> { + if (viewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && state.getEventType() == MegaEvent.EVENT_UPGRADE_SECURITY) { + replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); + } + return Unit.INSTANCE; + }); + collectFlows(); viewModel.onGetNumUnreadUserAlerts().observe(this, this::updateNumUnreadUserAlerts); diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index c52914206af..a3aa78a8740 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -59,6 +59,7 @@ import mega.privacy.android.domain.usecase.MonitorStorageStateEvent import mega.privacy.android.domain.usecase.SendStatisticsMediaDiscovery import mega.privacy.android.domain.usecase.billing.GetActiveSubscription import mega.privacy.android.domain.usecase.viewtype.MonitorViewType +import nz.mega.sdk.MegaEvent import nz.mega.sdk.MegaNode import nz.mega.sdk.MegaUser import nz.mega.sdk.MegaUserAlert @@ -167,11 +168,31 @@ class ManagerViewModel @Inject constructor( } } + viewModelScope.launch { + _state.update { + it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) + } + } + viewModelScope.launch { val incomingShares = getUnverifiedIncomingShares(SortOrder.ORDER_DEFAULT_ASC).size + _state.update { + it.copy(pendingActionsCount = _state.value.pendingActionsCount + incomingShares) + } + } + + viewModelScope.launch { val outgoingShares = getUnverifiedOutgoingShares(SortOrder.ORDER_DEFAULT_ASC).size _state.update { - it.copy(pendingActionsCount = incomingShares + outgoingShares) + it.copy(pendingActionsCount = _state.value.pendingActionsCount + outgoingShares) + } + } + + viewModelScope.launch { + updateGlobalEvents.collect { megaEvent -> + _state.update { + it.copy(eventType = megaEvent.peekContent().type) + } } } } @@ -217,6 +238,13 @@ class ManagerViewModel @Inject constructor( .map { Event(it) } .asLiveData() + + private val updateGlobalEvents: Flow> = _updates + .filterIsInstance() + .mapNotNull { (event) -> + event?.let { Event(it) } + } + private fun checkItemForInbox(updatedNodes: List) { //Verify is it is a new item to the inbox inboxNode?.let { node -> diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index 19126a222a8..c565d8d4abc 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -1,8 +1,11 @@ package mega.privacy.android.app.presentation.manager.model +import nz.mega.sdk.MegaEvent + /** * Manager UI state * + * @param rubbishBinParentHandle current rubbish bin parent handle * @param isFirstNavigationLevel true if the navigation level is the first level * @param sharesTab current tab in shares screen * @param transfersTab current tab in transfers screen @@ -11,9 +14,12 @@ package mega.privacy.android.app.presentation.manager.model * @param shouldStopCameraUpload camera upload should be stopped or not * @param shouldSendCameraBroadcastEvent broadcast event should be sent or not * @param nodeUpdateReceived one-off event to notify UI that a node update occurred + * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param pendingActionsCount Pending actions count + * @param eventType [MegaEvent] type */ data class ManagerState( + val rubbishBinParentHandle: Long = -1L, val isFirstNavigationLevel: Boolean = true, val sharesTab: SharesTab = SharesTab.INCOMING_TAB, val transfersTab: TransfersTab = TransfersTab.NONE, @@ -22,5 +28,7 @@ data class ManagerState( val shouldStopCameraUpload: Boolean = false, val shouldSendCameraBroadcastEvent: Boolean = false, val nodeUpdateReceived: Boolean = false, - val pendingActionsCount: Int = 0 + val isMandatoryFingerprintVerificationNeeded: Boolean = false, + val pendingActionsCount: Int = 0, + val eventType: Int = -1, ) From db35080a0210b70d6c6b875995fd58edf71cfa52 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 6 Jan 2023 13:02:28 +0530 Subject: [PATCH 168/334] dialog visibility check shifted to viewmodel. Unit test modified --- .../privacy/android/app/main/ManagerActivity.java | 2 +- .../app/presentation/manager/ManagerViewModel.kt | 10 ++++++---- .../app/presentation/manager/model/ManagerState.kt | 4 ++-- .../presentation/manager/ManagerViewModelTest.kt | 14 ++++++-------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 99987bc4c14..718ac3bb52b 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -1427,7 +1427,7 @@ protected void onCreate(Bundle savedInstanceState) { })); ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, state -> { - if (viewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && state.getEventType() == MegaEvent.EVENT_UPGRADE_SECURITY) { + if (viewModel.getState().getValue().getShowUpgradeSecurityAlert()) { replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); } return Unit.INSTANCE; diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index a3aa78a8740..d344aefc3b5 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -175,14 +175,14 @@ class ManagerViewModel @Inject constructor( } viewModelScope.launch { - val incomingShares = getUnverifiedIncomingShares(SortOrder.ORDER_DEFAULT_ASC).size + val incomingShares = getUnverifiedIncomingShares(getCloudSortOrder()).size _state.update { it.copy(pendingActionsCount = _state.value.pendingActionsCount + incomingShares) } } viewModelScope.launch { - val outgoingShares = getUnverifiedOutgoingShares(SortOrder.ORDER_DEFAULT_ASC).size + val outgoingShares = getUnverifiedOutgoingShares(getCloudSortOrder()).size _state.update { it.copy(pendingActionsCount = _state.value.pendingActionsCount + outgoingShares) } @@ -190,8 +190,10 @@ class ManagerViewModel @Inject constructor( viewModelScope.launch { updateGlobalEvents.collect { megaEvent -> - _state.update { - it.copy(eventType = megaEvent.peekContent().type) + if (_state.value.isMandatoryFingerprintVerificationNeeded && megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { + _state.update { + it.copy(showUpgradeSecurityAlert = true) + } } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index c565d8d4abc..0c39a300516 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -16,7 +16,7 @@ import nz.mega.sdk.MegaEvent * @param nodeUpdateReceived one-off event to notify UI that a node update occurred * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param pendingActionsCount Pending actions count - * @param eventType [MegaEvent] type + * @param showUpgradeSecurityAlert Boolean to decide whether to display security upgrade dialog or not */ data class ManagerState( val rubbishBinParentHandle: Long = -1L, @@ -30,5 +30,5 @@ data class ManagerState( val nodeUpdateReceived: Boolean = false, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val pendingActionsCount: Int = 0, - val eventType: Int = -1, + val showUpgradeSecurityAlert: Boolean = false, ) diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index a7e4a9f90d2..2e6ce45c653 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -82,14 +82,8 @@ class ManagerViewModelTest { }.thenReturn(true) } - private val getUnverifiedOutgoingShares = mock { - val shareData = ShareData("user", 8766L, 0, 987654678L, true) - onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) - } - private val getUnverifiedIncomingShares = mock { - val shareData = ShareData("user", 8766L, 0, 987654678L, true) - onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) - } + private val getUnverifiedOutgoingShares = mock() + private val getUnverifiedIncomingShares = mock() @get:Rule var instantExecutorRule = InstantTaskExecutorRule() @@ -421,6 +415,10 @@ class ManagerViewModelTest { @Test fun `test that pending actions count is not null`() = runTest { + val shareData = ShareData("user", 8766L, 0, 987654678L, true) + val shareData1 = ShareData("user", 8766L, 0, 987654678L, true) + whenever(getUnverifiedIncomingShares(getCloudSortOrder())).thenReturn(listOf(shareData, + shareData1)) setUnderTest() underTest.state.map { it.pendingActionsCount }.distinctUntilChanged().test { assertThat(awaitItem()).isEqualTo(2) From e57724c03131368684f302013b1b7ef54d8730fa Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 6 Jan 2023 13:16:33 +0530 Subject: [PATCH 169/334] unused import removed --- .../android/app/presentation/manager/model/ManagerState.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index 0c39a300516..706eab01286 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -1,7 +1,5 @@ package mega.privacy.android.app.presentation.manager.model -import nz.mega.sdk.MegaEvent - /** * Manager UI state * From a23d6e4f68eba81f234b4a8d0321acd1b3e19d43 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 6 Jan 2023 13:20:21 +0530 Subject: [PATCH 170/334] Unused imports removed --- .../main/java/mega/privacy/android/app/main/ManagerActivity.java | 1 - .../privacy/android/app/presentation/manager/ManagerViewModel.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 718ac3bb52b..c47b7feeb1d 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -452,7 +452,6 @@ import nz.mega.sdk.MegaChatRoom; import nz.mega.sdk.MegaContactRequest; import nz.mega.sdk.MegaError; -import nz.mega.sdk.MegaEvent; import nz.mega.sdk.MegaFolderInfo; import nz.mega.sdk.MegaNode; import nz.mega.sdk.MegaRequest; diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index d344aefc3b5..7516f66e0c1 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -34,7 +34,6 @@ import mega.privacy.android.app.presentation.manager.model.TransfersTab import mega.privacy.android.app.utils.livedata.SingleLiveEvent import mega.privacy.android.data.model.GlobalUpdate import mega.privacy.android.domain.entity.Product -import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.StorageState import mega.privacy.android.domain.entity.billing.MegaPurchase import mega.privacy.android.domain.entity.contacts.ContactRequest From 99e04c1aedb8fbae044e69749fde952dfb4a196e Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 6 Jan 2023 13:33:47 +0530 Subject: [PATCH 171/334] Modified Ui state variable name to a more meaningful name --- .../java/mega/privacy/android/app/main/ManagerActivity.java | 2 +- .../android/app/presentation/manager/ManagerViewModel.kt | 2 +- .../android/app/presentation/manager/model/ManagerState.kt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index c47b7feeb1d..fa340d29f37 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -1426,7 +1426,7 @@ protected void onCreate(Bundle savedInstanceState) { })); ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, state -> { - if (viewModel.getState().getValue().getShowUpgradeSecurityAlert()) { + if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); } return Unit.INSTANCE; diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index 7516f66e0c1..da11349e521 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -191,7 +191,7 @@ class ManagerViewModel @Inject constructor( updateGlobalEvents.collect { megaEvent -> if (_state.value.isMandatoryFingerprintVerificationNeeded && megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { _state.update { - it.copy(showUpgradeSecurityAlert = true) + it.copy(shouldAlertUserAboutSecurityUpgrade = true) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index 706eab01286..8dca9970c14 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -14,7 +14,7 @@ package mega.privacy.android.app.presentation.manager.model * @param nodeUpdateReceived one-off event to notify UI that a node update occurred * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param pendingActionsCount Pending actions count - * @param showUpgradeSecurityAlert Boolean to decide whether to display security upgrade dialog or not + * @param shouldAlertUserAboutSecurityUpgrade Boolean to decide whether to display security upgrade dialog or not */ data class ManagerState( val rubbishBinParentHandle: Long = -1L, @@ -28,5 +28,5 @@ data class ManagerState( val nodeUpdateReceived: Boolean = false, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val pendingActionsCount: Int = 0, - val showUpgradeSecurityAlert: Boolean = false, + val shouldAlertUserAboutSecurityUpgrade: Boolean = false, ) From 99c7d4503096cbe30f4c1d8b52958279ce332f46 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 9 Jan 2023 20:14:45 +0530 Subject: [PATCH 172/334] AND-15366 Added setSecureFlag in MegaApiGateway & added implementation in MegaApiFacade --- .../java/mega/privacy/android/data/facade/MegaApiFacade.kt | 3 +++ .../privacy/android/data/gateway/api/MegaApiGateway.kt | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index bbec3d70c50..61c237d0b3d 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -886,4 +886,7 @@ internal class MegaApiFacade @Inject constructor( override fun upgradeSecurity(listener: MegaRequestListenerInterface) = megaApi.upgradeSecurity(listener) + override fun setSecureFlag(enable: Boolean) { + megaApi.setSecureFlag(enable) + } } diff --git a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt index bfc8144b431..7351a0fe846 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt @@ -1709,4 +1709,11 @@ interface MegaApiGateway { * @param listener : Listener to track this request */ fun upgradeSecurity(listener: MegaRequestListenerInterface) + + /** + * Sets the secure flag to true or false while sharing a node + * + * @param enable : Boolean value + */ + fun setSecureFlag(enable: Boolean) } From 4fc1ede443ed9cf2bd4efab728bb69bfb360b521 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 9 Jan 2023 22:15:18 +0530 Subject: [PATCH 173/334] Code formatted --- .../java/mega/privacy/android/data/facade/MegaApiFacade.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index 61c237d0b3d..eee37d0e466 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -886,7 +886,6 @@ internal class MegaApiFacade @Inject constructor( override fun upgradeSecurity(listener: MegaRequestListenerInterface) = megaApi.upgradeSecurity(listener) - override fun setSecureFlag(enable: Boolean) { - megaApi.setSecureFlag(enable) - } + override fun setSecureFlag(enable: Boolean) = megaApi.setSecureFlag(enable) + } From 3681d795814d7dd70457a116fa8880d08bf53267 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 10 Jan 2023 09:44:37 +0530 Subject: [PATCH 174/334] Added a deprecated warning as the API is for testing purpose --- .../main/java/mega/privacy/android/data/facade/MegaApiFacade.kt | 1 + .../java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index eee37d0e466..0319080ef80 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -886,6 +886,7 @@ internal class MegaApiFacade @Inject constructor( override fun upgradeSecurity(listener: MegaRequestListenerInterface) = megaApi.upgradeSecurity(listener) + @Deprecated("This API is for testing purpose, will be deleted later") override fun setSecureFlag(enable: Boolean) = megaApi.setSecureFlag(enable) } diff --git a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt index 7351a0fe846..17ef0022c36 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt @@ -1715,5 +1715,6 @@ interface MegaApiGateway { * * @param enable : Boolean value */ + @Deprecated("This API is for testing purpose, will be deleted later") fun setSecureFlag(enable: Boolean) } From fe54009eb089b858e463ae4dc5c286980d7c0a47 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 10 Jan 2023 12:06:38 +0530 Subject: [PATCH 175/334] Added setSecureFlag use case --- .../mega/privacy/android/app/di/GetNodeModule.kt | 11 +++++++++++ .../android/domain/usecase/SetSecureFlag.kt | 14 ++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 domain/src/main/kotlin/mega/privacy/android/domain/usecase/SetSecureFlag.kt diff --git a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt index 040be52788f..6736fcc4c15 100644 --- a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt @@ -15,6 +15,7 @@ import mega.privacy.android.app.usecase.MoveNodeUseCase import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares +import mega.privacy.android.domain.usecase.SetSecureFlag import mega.privacy.android.domain.usecase.filenode.CopyNodeByHandle import mega.privacy.android.domain.usecase.filenode.CopyNodeByHandleChangingName import mega.privacy.android.domain.usecase.filenode.MoveNodeByHandle @@ -128,5 +129,15 @@ abstract class GetNodeModule { MoveNodeByHandle { nodeToCopy, newNodeParent -> moveNodeUseCase.move(nodeToCopy.longValue, newNodeParent.longValue).await() } + + /** + * Provides [SetSecureFlag] implementation + * + * @param megaNodeRepository [MegaNodeRepository] + * @return [SetSecureFlag] + */ + @Provides + fun provideSetSecureFlag(megaNodeRepository: MegaNodeRepository): SetSecureFlag = + SetSecureFlag(megaNodeRepository::setSecureFlag) } } diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/SetSecureFlag.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/SetSecureFlag.kt new file mode 100644 index 00000000000..425bf3a1a0b --- /dev/null +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/SetSecureFlag.kt @@ -0,0 +1,14 @@ +package mega.privacy.android.domain.usecase + +/** + * Interface to set secure flag value to true or false + */ +fun interface SetSecureFlag { + + /** + * Invoke + * + * @param enable : Boolean value + */ + suspend operator fun invoke(enable: Boolean) +} \ No newline at end of file From 50b08f8b0f06ac8c8c1c25081f68a0be1e70a7dd Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 12 Jan 2023 14:53:44 +0530 Subject: [PATCH 176/334] AND-15405 UI bugs for mandatory fingerprint verification are fixed --- .../assets/featuretoggle/feature_flags.json | 4 + .../mega/privacy/android/app/BaseActivity.kt | 2 +- .../android/app/main/ManagerActivity.java | 43 ++-- .../app/main/adapters/MegaNodeAdapter.java | 7 +- .../NodeOptionsBottomSheetDialogFragment.java | 187 ++++++++++-------- .../shares/incoming/IncomingSharesFragment.kt | 1 + .../incoming/IncomingSharesViewModel.kt | 11 +- .../incoming/model/IncomingSharesState.kt | 4 +- .../shares/outgoing/OutgoingSharesFragment.kt | 1 + .../outgoing/OutgoingSharesViewModel.kt | 11 +- .../outgoing/model/OutgoingSharesState.kt | 2 + .../layout/bottom_pending_actions_badge.xml | 29 +++ 12 files changed, 191 insertions(+), 111 deletions(-) create mode 100644 app/src/main/res/layout/bottom_pending_actions_badge.xml diff --git a/app/src/debug/assets/featuretoggle/feature_flags.json b/app/src/debug/assets/featuretoggle/feature_flags.json index dd9c6fb6fe3..676a4c1439e 100644 --- a/app/src/debug/assets/featuretoggle/feature_flags.json +++ b/app/src/debug/assets/featuretoggle/feature_flags.json @@ -2,5 +2,9 @@ { "name": "PermanentLogging", "value": true + }, + { + "name": "MandatoryFingerprintVerification", + "value": true } ] \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt index a1823be9708..b01ff13255c 100644 --- a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt @@ -463,7 +463,7 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - + megaApi.setSecureFlag(true) nameCollisionActivityContract = registerForActivityResult(NameCollisionActivityContract()) { result: String? -> if (result != null) { diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index fa340d29f37..588ad7e78eb 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -847,8 +847,6 @@ private enum HomepageScreen { int bottomNavigationCurrentItem = -1; View chatBadge; View callBadge; - View pendingActionsBadge; - BottomNavigationItemView sharedItemsView; private boolean joiningToChatLink; private String linkJoinToChatLink; @@ -1811,11 +1809,6 @@ public boolean onPreDraw() { itemView.addView(chatBadge); setChatBadge(); - // Navi button Shared Items - sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); - pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_chat_badge, menuView, false); - setPendingActionsBadge(); - callBadge = LayoutInflater.from(this).inflate(R.layout.bottom_call_badge, menuView, false); itemView.addView(callBadge); callBadge.setVisibility(View.GONE); @@ -2607,20 +2600,20 @@ public void onPageScrollStateChanged(int state) { } ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { - addUnverifiedIncomingCountBadge(incomingSharesState.getUnVerifiedIncomingNodes().size()); + addUnverifiedIncomingCountBadge(incomingSharesState.getUnverifiedIncomingShares().size()); } return Unit.INSTANCE; }); ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { - addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnVerifiedOutgoingNodes().size()); + addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnverifiedOutgoingShares().size()); } return Unit.INSTANCE; }); - } - + setPendingActionsBadge(menuView); + } /** * collecting Flows from ViewModels @@ -10288,6 +10281,7 @@ public AndroidCompletedTransfer getSelectedTransfer() { } public MegaNode getSelectedNode() { + callOpenShareDialog(); return selectedNode; } @@ -10520,17 +10514,30 @@ public void setChatBadge() { } } - public void setPendingActionsBadge() { - if (viewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded()) { - sharedItemsView.addView(pendingActionsBadge); - ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { - TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.chat_badge_text); - tvPendingActionsCount.setText(managerState.getPendingActionsCount()); - return Unit.INSTANCE; + private void callOpenShareDialog() { + if (searchViewModel.getState().getValue().isMandatoryFingerPrintVerificationRequired()) { + megaApi.openShareDialog(selectedNode, new OptionalMegaRequestListenerInterface() { + @Override + public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { + super.onRequestFinish(api, request, error); + } }); } } + public void setPendingActionsBadge(BottomNavigationMenuView menuView) { + ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { + if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { + BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); + View pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_pending_actions_badge, menuView, false); + sharedItemsView.addView(pendingActionsBadge); + TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.pending_actions_badge_text); + tvPendingActionsCount.setText(String.valueOf(managerState.getPendingActionsCount())); + } + return Unit.INSTANCE; + }); + } + private void setCallBadge() { if (!viewModel.isConnected() || megaChatApi.getNumCalls() <= 0 || (megaChatApi.getNumCalls() == 1 && participatingInACall())) { callBadge.setVisibility(View.GONE); diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index bbddf9a4917..481cc412967 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -68,6 +68,7 @@ import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -144,9 +145,9 @@ public class MegaNodeAdapter extends RecyclerView.Adapter unverifiedIncomingNodeHandles; - private Set unverifiedOutgoingNodeHandles; - private Boolean isMandatoryFingerprintVerificationNeeded; + private Set unverifiedIncomingNodeHandles = new HashSet<>(); + private Set unverifiedOutgoingNodeHandles = new HashSet<>(); + private boolean isMandatoryFingerprintVerificationNeeded; public static class ViewHolderBrowser extends RecyclerView.ViewHolder { diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 114a1c936e9..be190728956 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -96,11 +96,14 @@ import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity; import mega.privacy.android.app.presentation.manager.model.SharesTab; import mega.privacy.android.app.presentation.search.SearchViewModel; +import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel; +import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel; import mega.privacy.android.app.utils.AlertDialogUtil; import mega.privacy.android.app.utils.Constants; import mega.privacy.android.app.utils.MegaNodeUtil; import mega.privacy.android.app.utils.StringResourcesUtils; import mega.privacy.android.app.utils.ViewUtils; +import mega.privacy.android.domain.entity.ShareData; import mega.privacy.android.domain.entity.SortOrder; import nz.mega.sdk.MegaApiJava; import nz.mega.sdk.MegaError; @@ -174,6 +177,9 @@ public class NodeOptionsBottomSheetDialogFragment extends BaseBottomSheetDialogF private MegaUser user; + private IncomingSharesViewModel incomingSharesViewModel; + private OutgoingSharesViewModel outgoingSharesViewModel; + public NodeOptionsBottomSheetDialogFragment(int mode) { if (mode >= DEFAULT_MODE && mode <= FAVOURITES_MODE) { mMode = mode; @@ -184,14 +190,19 @@ public NodeOptionsBottomSheetDialogFragment() { mMode = DEFAULT_MODE; } + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class); + incomingSharesViewModel = new ViewModelProvider(requireActivity()).get(IncomingSharesViewModel.class); + outgoingSharesViewModel = new ViewModelProvider(requireActivity()).get(OutgoingSharesViewModel.class); + } + @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { contentView = View.inflate(getContext(), R.layout.bottom_sheet_node_item, null); itemsLayout = contentView.findViewById(R.id.items_layout_bottom_sheet_node); - - searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class); - if (savedInstanceState != null) { long handle = savedInstanceState.getLong(HANDLE, INVALID_HANDLE); node = megaApi.getNodeByHandle(handle); @@ -224,6 +235,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat nodeInfo = contentView.findViewById(R.id.node_info_text); ImageView nodeVersionsIcon = contentView.findViewById(R.id.node_info_versions_icon); + LinearLayout optionOffline = contentView.findViewById(R.id.option_offline_layout); + ImageView permissionsIcon = contentView.findViewById(R.id.permissions_icon); LinearLayout optionEdit = contentView.findViewById(R.id.edit_file_option); @@ -239,7 +252,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat TextView optionLabelCurrent = contentView.findViewById(R.id.option_label_current); // counterSave TextView optionDownload = contentView.findViewById(R.id.download_option); - LinearLayout optionOffline = contentView.findViewById(R.id.option_offline_layout); + SwitchMaterial offlineSwitch = contentView.findViewById(R.id.file_properties_switch); // counterShares TextView optionLink = contentView.findViewById(R.id.link_option); @@ -331,81 +344,47 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } if (isOnline(requireContext())) { - ViewExtensionsKt.collectFlow(requireActivity(), searchViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (state.isMandatoryFingerPrintVerificationRequired()) { - showOwnerSharedFolder(); - TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); - optionVerifyUser.setText(StringResourcesUtils.getString(R.string.shared_items_bottom_sheet_menu_verify_user, getMegaUserNameDB(user))); - nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); - optionVerifyUser.setVisibility(View.VISIBLE); - optionVerifyUser.setOnClickListener(this); - - //Removing the click listener & making it View.GONE - optionDownload.setOnClickListener(null); - optionDownload.setVisibility(View.GONE); + nodeName.setText(node.getName()); + if (node.isFolder()) { + optionVersionsLayout.setVisibility(View.GONE); + nodeInfo.setText(getMegaNodeFolderInfo(node)); + nodeVersionsIcon.setVisibility(View.GONE); + + nodeThumb.setImageResource(getFolderIcon(node, drawerItem)); - //Removing the click listener & making it View.GONE - optionOffline.setOnClickListener(null); + if (isEmptyFolder(node)) { + counterSave--; optionOffline.setVisibility(View.GONE); + } - separatorDownload.setVisibility(View.GONE); - separatorLabel.setVisibility(View.GONE); - separatorOpen.setVisibility(View.GONE); - separatorModify.setVisibility(View.GONE); - separatorShares.setVisibility(View.GONE); + counterShares--; + optionSendChat.setVisibility(View.GONE); + } else { + if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize()) + && accessLevel >= MegaShare.ACCESS_READWRITE) { + optionEdit.setVisibility(View.VISIBLE); + } - //Removing the click listener & making it View.GONE - optionSendChat.setOnClickListener(null); - optionSendChat.setVisibility(View.GONE); + nodeInfo.setText(getFileInfo(node)); - //Removing the click listener & making it View.GONE - optionCopy.setOnClickListener(null); - optionCopy.setVisibility(View.GONE); + if (megaApi.hasVersions(node)) { + nodeVersionsIcon.setVisibility(View.VISIBLE); + optionVersionsLayout.setVisibility(View.VISIBLE); + versions.setText(String.valueOf(megaApi.getNumVersions(node))); } else { - nodeName.setText(node.getName()); - if (node.isFolder()) { - optionVersionsLayout.setVisibility(View.GONE); - nodeInfo.setText(getMegaNodeFolderInfo(node)); - nodeVersionsIcon.setVisibility(View.GONE); - - nodeThumb.setImageResource(getFolderIcon(node, drawerItem)); - - if (isEmptyFolder(node)) { - counterSave--; - optionOffline.setVisibility(View.GONE); - } - - counterShares--; - optionSendChat.setVisibility(View.GONE); - } else { - if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize()) - && accessLevel >= MegaShare.ACCESS_READWRITE) { - optionEdit.setVisibility(View.VISIBLE); - } - - nodeInfo.setText(getFileInfo(node)); - - if (megaApi.hasVersions(node)) { - nodeVersionsIcon.setVisibility(View.VISIBLE); - optionVersionsLayout.setVisibility(View.VISIBLE); - versions.setText(String.valueOf(megaApi.getNumVersions(node))); - } else { - nodeVersionsIcon.setVisibility(View.GONE); - optionVersionsLayout.setVisibility(View.GONE); - } + nodeVersionsIcon.setVisibility(View.GONE); + optionVersionsLayout.setVisibility(View.GONE); + } - setNodeThumbnail(requireContext(), node, nodeThumb); + setNodeThumbnail(requireContext(), node, nodeThumb); - if (isTakenDown) { - counterShares--; - optionSendChat.setVisibility(View.GONE); - } else { - optionSendChat.setVisibility(View.VISIBLE); - } - } + if (isTakenDown) { + counterShares--; + optionSendChat.setVisibility(View.GONE); + } else { + optionSendChat.setVisibility(View.VISIBLE); } - return Unit.INSTANCE; - }); + } } if (isTakenDown) { @@ -742,6 +721,23 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } super.onViewCreated(view, savedInstanceState); + if(nC.nodeComesFromIncoming(node)) { + ViewExtensionsKt.collectFlow(requireActivity(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { + if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE) { + setUnverifiedNodeUserName(incomingSharesViewModel.getState().getValue().getUnverifiedIncomingShares()); + hideNodeActions(); + } + return Unit.INSTANCE; + }); + } else { + ViewExtensionsKt.collectFlow(requireActivity(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { + if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE) { + setUnverifiedNodeUserName(outgoingSharesViewModel.getState().getValue().getUnverifiedOutgoingShares()); + hideNodeActions(); + } + return Unit.INSTANCE; + }); + } } @Override @@ -882,6 +878,40 @@ private void showOwnerSharedFolder() { } } } + private void setUnverifiedNodeUserName(List shareDataList) { + for (int j = 0; j < shareDataList.size(); j++) { + ShareData mS = shareDataList.get(j); + if (mS.getNodeHandle() == node.getHandle()) { + user = megaApi.getContact(mS.getUser()); + if (user != null) { + nodeInfo.setText(getMegaUserNameDB(user)); + } else { + nodeInfo.setText(mS.getUser()); + } + } + } + } + + private void hideNodeActions() { + TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); + optionVerifyUser.setText(StringResourcesUtils.getString(R.string.shared_items_bottom_sheet_menu_verify_user, getMegaUserNameDB(user))); + TextView nodeName = contentView.findViewById(R.id.node_name_text); + nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + optionVerifyUser.setVisibility(View.VISIBLE); + optionVerifyUser.setOnClickListener(this); + + contentView.findViewById(R.id.favorite_option).setVisibility(View.GONE); + contentView.findViewById(R.id.rename_option).setVisibility(View.GONE); + contentView.findViewById(R.id.link_option).setVisibility(View.GONE); + contentView.findViewById(R.id.remove_option).setVisibility(View.GONE); + contentView.findViewById(R.id.download_option).setVisibility(View.GONE); + contentView.findViewById(R.id.option_offline_layout).setVisibility(View.GONE); + contentView.findViewById(R.id.copy_option).setVisibility(View.GONE); + contentView.findViewById(R.id.rubbish_bin_option).setVisibility(View.GONE); + contentView.findViewById(R.id.share_option).setVisibility(View.GONE); + contentView.findViewById(R.id.share_folder_option).setVisibility(View.GONE); + contentView.findViewById(R.id.clear_share_option).setVisibility(View.GONE); + } @SuppressLint("NonConstantResourceId") @Override @@ -962,22 +992,7 @@ public void onClick(View v) { break; case R.id.share_folder_option: - ViewExtensionsKt.collectFlow(requireActivity(), searchViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (state.isMandatoryFingerPrintVerificationRequired()) { - megaApi.openShareDialog(node, new OptionalMegaRequestListenerInterface() { - @Override - public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { - super.onRequestFinish(api, request, error); - if (error.getErrorCode() == MegaError.API_OK) { - showShareFolderOptions(); - } - } - }); - } else { - showShareFolderOptions(); - } - return Unit.INSTANCE; - }); + showShareFolderOptions(); break; case R.id.clear_share_option: diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 9d62608e24c..1ab2317f119 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -241,6 +241,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) adapter?.setUnverifiedIncomingNodes(it.unVerifiedIncomingNodes) + adapter?.notifyDataSetChanged() updateNodes(it.unVerifiedIncomingNodes + it.nodes) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 37d6e7b0e2b..7bcc95ae45b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -73,7 +73,16 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() - val unverifiedIncomingNodes = getUnverifiedIncomingShares(_state.value.sortOrder) + } + + viewModelScope.launch { + _state.update { + it.copy(unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder)) + } + } + + viewModelScope.launch { + val unverifiedIncomingNodes = _state.value.unverifiedIncomingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index 3baf88523a9..e758b69c7b2 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -15,7 +15,8 @@ import nz.mega.sdk.MegaNode * @param isLoading true if the nodes are loading * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed - * @param unVerifiedIncomingNodes List of unverified Incoming [MegaNode] + * @param unverifiedIncomingShares List of unverified incoming [ShareData] + * @param unVerifiedIncomingNodes List of unverified incoming [MegaNode] */ data class IncomingSharesState( val incomingHandle: Long = -1L, @@ -26,6 +27,7 @@ data class IncomingSharesState( val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, + val unverifiedIncomingShares: List = emptyList(), val unVerifiedIncomingNodes: List = emptyList(), ) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 3ab104c61fc..b377a5d908d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -235,6 +235,7 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) adapter?.setUnverifiedOutgoingNodes(it.unVerifiedOutgoingNodes) + adapter?.notifyDataSetChanged() updateNodes(it.unVerifiedOutgoingNodes + it.nodes) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index bc76d911b88..3141de62510 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -58,7 +58,16 @@ class OutgoingSharesViewModel @Inject constructor( viewModelScope.launch { isMandatoryFingerprintRequired() - val unverifiedOutgoingNodes = getUnverifiedOutgoingShares(_state.value.sortOrder) + } + + viewModelScope.launch { + _state.update { + it.copy(unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder)) + } + } + + viewModelScope.launch { + val unverifiedOutgoingNodes = _state.value.unverifiedOutgoingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } _state.update { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index b9b121326e1..66c7394278e 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -15,6 +15,7 @@ import nz.mega.sdk.MegaNode * @param isLoading true if the nodes are loading * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed + * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodes List of Unverified outgoing [MegaNode] */ data class OutgoingSharesState( @@ -26,6 +27,7 @@ data class OutgoingSharesState( val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, + val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodes: List = emptyList(), ) { diff --git a/app/src/main/res/layout/bottom_pending_actions_badge.xml b/app/src/main/res/layout/bottom_pending_actions_badge.xml new file mode 100644 index 00000000000..a5cf0a8a719 --- /dev/null +++ b/app/src/main/res/layout/bottom_pending_actions_badge.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file From 1cebcda2befa4d4df9d557adc35673fbd7273ae0 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 12 Jan 2023 15:12:43 +0530 Subject: [PATCH 177/334] text color changed to black --- .../authenticitycredendials/view/AuthenticityCredentialsView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt index 28f0aaf2a23..3c648f93bdd 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt @@ -152,7 +152,7 @@ fun ContactCredentials( bottom = 14.dp, end = 48.dp), style = MaterialTheme.typography.body2, - color = if (MaterialTheme.colors.isLight) black else white_alpha_087, + color = black, text = stringResource(id = R.string.shared_items_verify_credentials_verify_person_banner_label)) IconButton( From 6d6e20420759c168b35cdc668577ce3683cceced Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 12 Jan 2023 20:30:39 +0530 Subject: [PATCH 178/334] AND - 15405 Unverified node UI modified to match design --- .../app/main/adapters/MegaNodeAdapter.java | 25 +++++++++++-------- .../shares/incoming/IncomingSharesFragment.kt | 5 ++-- .../shares/outgoing/OutgoingSharesFragment.kt | 5 ++-- .../outgoing/OutgoingSharesViewModel.kt | 9 ------- .../outgoing/model/OutgoingSharesState.kt | 1 - .../outgoing/OutgoingSharesViewModelTest.kt | 2 +- 6 files changed, 19 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 481cc412967..9b1a4a35456 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -102,6 +102,7 @@ import mega.privacy.android.app.utils.ThumbnailUtils; import mega.privacy.android.data.database.DatabaseHandler; import mega.privacy.android.data.model.MegaContactDB; +import mega.privacy.android.domain.entity.ShareData; import mega.privacy.android.domain.entity.SortOrder; import nz.mega.sdk.MegaApiAndroid; import nz.mega.sdk.MegaNode; @@ -1523,32 +1524,34 @@ public void setMandatoryFingerprintVerificationValue(boolean isVerificationNeede /** * Adds unverified incoming nodes to Set * - * @param nodes - List of incoming [MegaNode] + * @param shares - List of incoming [ShareData] */ - public void setUnverifiedIncomingNodes(List nodes) { + public void setUnverifiedIncomingNodes(List shares) { unverifiedIncomingNodeHandles.clear(); - nodes.forEach(megaNode -> unverifiedIncomingNodeHandles.add(megaNode.getHandle())); + shares.forEach(share -> unverifiedIncomingNodeHandles.add(share.getNodeHandle())); } /** * Adds unverified outgoing nodes to Set * - * @param nodes - List of outgoing [MegaNode] + * @param shares - List of outgoing [ShareData] */ - public void setUnverifiedOutgoingNodes(List nodes) { + public void setUnverifiedOutgoingNodes(List shares) { unverifiedOutgoingNodeHandles.clear(); - nodes.forEach(megaNode -> unverifiedOutgoingNodeHandles.add(megaNode.getHandle())); + shares.forEach(share -> unverifiedOutgoingNodeHandles.add(share.getNodeHandle())); } /** * Function to check if current node is unverified & show Ui items accordingly * - * @param unverifiedNodes Unverified Nodes List - * @param currentNode Current node from adapter - * @param holder [ViewHolderBrowserList] + * @param unverifiedNodeHandles Unverified Node handles list + * @param currentNode Current node from adapter + * @param holder [ViewHolderBrowserList] */ - private void showUnverifiedNodeUi(Set unverifiedNodes, MegaNode currentNode, ViewHolderBrowserList holder) { - if (unverifiedNodes.contains(currentNode.getHandle())) { + private void showUnverifiedNodeUi(Set unverifiedNodeHandles, MegaNode currentNode, ViewHolderBrowserList holder) { + if (unverifiedNodeHandles.contains(currentNode.getHandle())) { + holder.permissionsIcon.setVisibility(View.VISIBLE); + holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 1ab2317f119..84535cb35ec 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -240,9 +240,8 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { hideActionMode() setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) - adapter?.setUnverifiedIncomingNodes(it.unVerifiedIncomingNodes) - adapter?.notifyDataSetChanged() - updateNodes(it.unVerifiedIncomingNodes + it.nodes) + adapter?.setUnverifiedIncomingNodes(it.unverifiedIncomingShares) + updateNodes(it.nodes) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index b377a5d908d..cb1639c360d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -234,9 +234,8 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { hideActionMode() setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) - adapter?.setUnverifiedOutgoingNodes(it.unVerifiedOutgoingNodes) - adapter?.notifyDataSetChanged() - updateNodes(it.unVerifiedOutgoingNodes + it.nodes) + adapter?.setUnverifiedOutgoingNodes(it.unverifiedOutgoingShares) + updateNodes(it.nodes) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 3141de62510..365bcebd357 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -65,15 +65,6 @@ class OutgoingSharesViewModel @Inject constructor( it.copy(unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder)) } } - - viewModelScope.launch { - val unverifiedOutgoingNodes = _state.value.unverifiedOutgoingShares - .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } - _state.update { - it.copy(unVerifiedOutgoingNodes = unverifiedOutgoingNodes) - } - } } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index 66c7394278e..ead168c2c42 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -28,7 +28,6 @@ data class OutgoingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), - val unVerifiedOutgoingNodes: List = emptyList(), ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index f7bfb4d5a09..a97d38b03e3 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -450,7 +450,7 @@ class OutgoingSharesViewModelTest { whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() initViewModel() - underTest.state.map { it.unVerifiedOutgoingNodes }.distinctUntilChanged() + underTest.state.map { it.unverifiedOutgoingShares }.distinctUntilChanged() .test { assertThat(awaitItem().size).isEqualTo(1) } From fef46d488f7875ea2dbe560e06955ea6e896623a Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 12 Jan 2023 22:12:07 +0530 Subject: [PATCH 179/334] AND-15405 Unverified check added to Bottom sheet dialog actions --- .../NodeOptionsBottomSheetDialogFragment.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index be190728956..e49933a16a3 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -76,7 +76,9 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Set; import kotlin.Unit; import mega.privacy.android.app.MegaOffline; @@ -179,6 +181,7 @@ public class NodeOptionsBottomSheetDialogFragment extends BaseBottomSheetDialogF private IncomingSharesViewModel incomingSharesViewModel; private OutgoingSharesViewModel outgoingSharesViewModel; + private Set unverifiedHandles = new HashSet<>(); public NodeOptionsBottomSheetDialogFragment(int mode) { if (mode >= DEFAULT_MODE && mode <= FAVOURITES_MODE) { @@ -723,16 +726,20 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); if(nC.nodeComesFromIncoming(node)) { ViewExtensionsKt.collectFlow(requireActivity(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE) { - setUnverifiedNodeUserName(incomingSharesViewModel.getState().getValue().getUnverifiedIncomingShares()); + if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() + && mMode == SHARED_ITEMS_MODE + && isNodeUnverified(state.getUnverifiedIncomingShares())) { + setUnverifiedNodeUserName(state.getUnverifiedIncomingShares()); hideNodeActions(); } return Unit.INSTANCE; }); } else { ViewExtensionsKt.collectFlow(requireActivity(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE) { - setUnverifiedNodeUserName(outgoingSharesViewModel.getState().getValue().getUnverifiedOutgoingShares()); + if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() + && mMode == SHARED_ITEMS_MODE + && isNodeUnverified(state.getUnverifiedOutgoingShares())) { + setUnverifiedNodeUserName(state.getUnverifiedOutgoingShares()); hideNodeActions(); } return Unit.INSTANCE; @@ -913,6 +920,12 @@ private void hideNodeActions() { contentView.findViewById(R.id.clear_share_option).setVisibility(View.GONE); } + private boolean isNodeUnverified(List shareDataList) { + unverifiedHandles.clear(); + shareDataList.forEach(shareData -> unverifiedHandles.add(shareData.getNodeHandle())); + return unverifiedHandles.contains(node.getHandle()); + } + @SuppressLint("NonConstantResourceId") @Override public void onClick(View v) { From 6ed14d5329216aa2fc6b0cdd2ccf58cd77e6e2d0 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 13 Jan 2023 15:03:50 +0530 Subject: [PATCH 180/334] AND-15405 Code optimised & comments resolved --- .../app/main/adapters/MegaNodeAdapter.java | 46 +++++++++---------- .../NodeOptionsBottomSheetDialogFragment.java | 10 ++-- .../shares/incoming/IncomingSharesFragment.kt | 2 +- .../incoming/IncomingSharesViewModel.kt | 16 +++---- .../incoming/model/IncomingSharesState.kt | 4 +- .../shares/outgoing/OutgoingSharesFragment.kt | 2 +- .../outgoing/OutgoingSharesViewModel.kt | 9 +++- .../outgoing/model/OutgoingSharesState.kt | 3 +- .../incoming/IncomingSharesViewModelTest.kt | 2 +- 9 files changed, 49 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 9b1a4a35456..e418fc0d1d7 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -146,8 +146,8 @@ public class MegaNodeAdapter extends RecyclerView.Adapter unverifiedIncomingNodeHandles = new HashSet<>(); - private Set unverifiedOutgoingNodeHandles = new HashSet<>(); + private final Set unverifiedIncomingNodeHandles = new HashSet<>(); + private final Set unverifiedOutgoingNodeHandles = new HashSet<>(); private boolean isMandatoryFingerprintVerificationNeeded; public static class ViewHolderBrowser extends RecyclerView.ViewHolder { @@ -1092,8 +1092,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if (isMandatoryFingerprintVerificationNeeded && !unverifiedIncomingNodeHandles.isEmpty()) { - showUnverifiedNodeUi(unverifiedIncomingNodeHandles, node, holder); + if (isMandatoryFingerprintVerificationNeeded + && !unverifiedIncomingNodeHandles.isEmpty() + && unverifiedIncomingNodeHandles.contains(node.getHandle())) { + showUnverifiedNodeUi(holder); } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { @@ -1103,8 +1105,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else if (type == OUTGOING_SHARES_ADAPTER) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - if (isMandatoryFingerprintVerificationNeeded && !unverifiedOutgoingNodeHandles.isEmpty()) { - showUnverifiedNodeUi(unverifiedOutgoingNodeHandles, node, holder); + if (isMandatoryFingerprintVerificationNeeded + && !unverifiedOutgoingNodeHandles.isEmpty() + && unverifiedOutgoingNodeHandles.contains(node.getHandle())) { + showUnverifiedNodeUi(holder); } } } else { @@ -1524,36 +1528,32 @@ public void setMandatoryFingerprintVerificationValue(boolean isVerificationNeede /** * Adds unverified incoming nodes to Set * - * @param shares - List of incoming [ShareData] + * @param handles - List of incoming node handles */ - public void setUnverifiedIncomingNodes(List shares) { + public void setUnverifiedIncomingNodeHandles(List handles) { unverifiedIncomingNodeHandles.clear(); - shares.forEach(share -> unverifiedIncomingNodeHandles.add(share.getNodeHandle())); + unverifiedIncomingNodeHandles.addAll(handles); } /** * Adds unverified outgoing nodes to Set * - * @param shares - List of outgoing [ShareData] + * @param handles - List of outgoing node handles */ - public void setUnverifiedOutgoingNodes(List shares) { + public void setUnverifiedOutgoingNodeHandles(List handles) { unverifiedOutgoingNodeHandles.clear(); - shares.forEach(share -> unverifiedOutgoingNodeHandles.add(share.getNodeHandle())); + unverifiedOutgoingNodeHandles.addAll(handles); } /** - * Function to check if current node is unverified & show Ui items accordingly + * Function to show Unverified node UI items accordingly * - * @param unverifiedNodeHandles Unverified Node handles list - * @param currentNode Current node from adapter - * @param holder [ViewHolderBrowserList] + * @param holder [ViewHolderBrowserList] */ - private void showUnverifiedNodeUi(Set unverifiedNodeHandles, MegaNode currentNode, ViewHolderBrowserList holder) { - if (unverifiedNodeHandles.contains(currentNode.getHandle())) { - holder.permissionsIcon.setVisibility(View.VISIBLE); - holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); - holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); - holder.permissionsIcon.setImageResource(R.drawable.serious_warning); - } + private void showUnverifiedNodeUi(ViewHolderBrowserList holder) { + holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); + holder.permissionsIcon.setVisibility(View.VISIBLE); + holder.permissionsIcon.setImageResource(R.drawable.serious_warning); } } diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index e49933a16a3..4dba22ed35c 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -728,7 +728,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat ViewExtensionsKt.collectFlow(requireActivity(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE - && isNodeUnverified(state.getUnverifiedIncomingShares())) { + && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedIncomingShares()); hideNodeActions(); } @@ -738,7 +738,7 @@ && isNodeUnverified(state.getUnverifiedIncomingShares())) { ViewExtensionsKt.collectFlow(requireActivity(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE - && isNodeUnverified(state.getUnverifiedOutgoingShares())) { + && isNodeUnverified(state.getUnVerifiedOutgoingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedOutgoingShares()); hideNodeActions(); } @@ -920,10 +920,8 @@ private void hideNodeActions() { contentView.findViewById(R.id.clear_share_option).setVisibility(View.GONE); } - private boolean isNodeUnverified(List shareDataList) { - unverifiedHandles.clear(); - shareDataList.forEach(shareData -> unverifiedHandles.add(shareData.getNodeHandle())); - return unverifiedHandles.contains(node.getHandle()); + private boolean isNodeUnverified(List shareDataList) { + return shareDataList.contains(node.getHandle()); } @SuppressLint("NonConstantResourceId") diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 84535cb35ec..6bcc1f19128 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -240,7 +240,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { hideActionMode() setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) - adapter?.setUnverifiedIncomingNodes(it.unverifiedIncomingShares) + adapter?.setUnverifiedIncomingNodeHandles(it.unVerifiedIncomingNodeHandles) updateNodes(it.nodes) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 7bcc95ae45b..1b52c2e1d7f 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -76,17 +76,15 @@ class IncomingSharesViewModel @Inject constructor( } viewModelScope.launch { - _state.update { - it.copy(unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder)) - } - } - - viewModelScope.launch { - val unverifiedIncomingNodes = _state.value.unverifiedIncomingShares + val unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder) + val handles = unverifiedIncomingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle) } + .mapNotNull { shareData -> + getNodeByHandle(shareData.nodeHandle)?.handle + } _state.update { - it.copy(unVerifiedIncomingNodes = unverifiedIncomingNodes) + it.copy(unverifiedIncomingShares = unverifiedIncomingShares, + unVerifiedIncomingNodeHandles = handles) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index e758b69c7b2..56ee6aaf12c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -16,7 +16,7 @@ import nz.mega.sdk.MegaNode * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedIncomingShares List of unverified incoming [ShareData] - * @param unVerifiedIncomingNodes List of unverified incoming [MegaNode] + * @param unVerifiedIncomingNodeHandles List of unverified incoming node handles */ data class IncomingSharesState( val incomingHandle: Long = -1L, @@ -28,7 +28,7 @@ data class IncomingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedIncomingShares: List = emptyList(), - val unVerifiedIncomingNodes: List = emptyList(), + val unVerifiedIncomingNodeHandles: List = emptyList(), ) { /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index cb1639c360d..4810411a01a 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -234,7 +234,7 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { hideActionMode() setEmptyView(it.isInvalidHandle) adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) - adapter?.setUnverifiedOutgoingNodes(it.unverifiedOutgoingShares) + adapter?.setUnverifiedOutgoingNodeHandles(it.unVerifiedOutgoingNodeHandles) updateNodes(it.nodes) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 365bcebd357..476ae514328 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -61,8 +61,15 @@ class OutgoingSharesViewModel @Inject constructor( } viewModelScope.launch { + val unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder) + val handles = unverifiedOutgoingShares + .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + .mapNotNull { shareData -> + getNodeByHandle(shareData.nodeHandle)?.handle + } _state.update { - it.copy(unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder)) + it.copy(unverifiedOutgoingShares = unverifiedOutgoingShares, + unVerifiedOutgoingNodeHandles = handles) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index ead168c2c42..b326a7e9987 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -16,7 +16,7 @@ import nz.mega.sdk.MegaNode * @param sortOrder current sort order * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] - * @param unVerifiedOutgoingNodes List of Unverified outgoing [MegaNode] + * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -28,6 +28,7 @@ data class OutgoingSharesState( val sortOrder: SortOrder = SortOrder.ORDER_NONE, val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), + val unVerifiedOutgoingNodeHandles: List = emptyList(), ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 493fd0719e1..d9dea96febf 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -526,7 +526,7 @@ class IncomingSharesViewModelTest { whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() initViewModel() - underTest.state.map { it.unVerifiedIncomingNodes }.distinctUntilChanged() + underTest.state.map { it.unverifiedIncomingShares }.distinctUntilChanged() .test { assertThat(awaitItem().size).isEqualTo(1) } From f988b5ac1c228e6fa437219fb5ee944c9a6e15dd Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 17 Jan 2023 18:31:26 +0530 Subject: [PATCH 181/334] AND-15405 Incoming nodes bug fixed --- .../shares/incoming/IncomingSharesViewModel.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 1b52c2e1d7f..36e0484a396 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -77,19 +77,24 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { val unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder) + val unverifiedIncomingNodes = unverifiedIncomingShares + .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + .mapNotNull { shareData -> + getNodeByHandle(shareData.nodeHandle) + } val handles = unverifiedIncomingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .mapNotNull { shareData -> getNodeByHandle(shareData.nodeHandle)?.handle } _state.update { - it.copy(unverifiedIncomingShares = unverifiedIncomingShares, + it.copy(nodes = unverifiedIncomingNodes, + unverifiedIncomingShares = unverifiedIncomingShares, unVerifiedIncomingNodeHandles = handles) } } } - /** * Refresh incoming shares node */ From 8ec065f9e12b5b4de6e086403e2752c7dcffd399 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 23 Jan 2023 17:16:02 +1300 Subject: [PATCH 182/334] AND-15464 Unverified incoming nodes displayed under incoming shares tab --- .../incoming/IncomingSharesViewModelTest.kt | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index d9dea96febf..6baf6512a1e 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.AuthorizeNode @@ -27,6 +28,7 @@ import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import nz.mega.sdk.MegaNode +import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test @@ -74,6 +76,11 @@ class IncomingSharesViewModelTest { initViewModel() } + @After + fun tearDown() { + Dispatchers.resetMain() + } + private fun initViewModel() { underTest = IncomingSharesViewModel( getNodeByHandle, @@ -243,13 +250,13 @@ class IncomingSharesViewModelTest { @Test fun `test that is invalid handle is set to false when call set incoming tree depth with valid handle`() = runTest { - whenever(getIncomingSharesChildrenNode(any())).thenReturn(mock()) + whenever(getIncomingSharesChildrenNode(any())).thenReturn(listOf(mock())) whenever(getNodeByHandle(any())).thenReturn(mock()) underTest.state.map { it.isInvalidHandle }.distinctUntilChanged() .test { assertThat(awaitItem()).isEqualTo(true) - underTest.setIncomingTreeDepth(any(), 123456789L) + underTest.setIncomingTreeDepth(1, 123456789L) assertThat(awaitItem()).isEqualTo(false) } } @@ -257,15 +264,15 @@ class IncomingSharesViewModelTest { @Test fun `test that is invalid handle is set to true when call set incoming tree depth with invalid handle`() = runTest { - whenever(getIncomingSharesChildrenNode(any())).thenReturn(mock()) + whenever(getIncomingSharesChildrenNode(any())).thenReturn(listOf(mock())) whenever(getNodeByHandle(any())).thenReturn(mock()) underTest.state.map { it.isInvalidHandle }.distinctUntilChanged() .test { assertThat(awaitItem()).isEqualTo(true) - underTest.setIncomingTreeDepth(any(), 123456789L) + underTest.setIncomingTreeDepth(1, 123456789L) assertThat(awaitItem()).isEqualTo(false) - underTest.setIncomingTreeDepth(any(), -1L) + underTest.setIncomingTreeDepth(1, -1L) assertThat(awaitItem()).isEqualTo(true) } } @@ -274,18 +281,18 @@ class IncomingSharesViewModelTest { @Test fun `test that is invalid handle is set to true when cannot retrieve node`() = runTest { - whenever(getIncomingSharesChildrenNode(any())).thenReturn(mock()) + whenever(getIncomingSharesChildrenNode(any())).thenReturn(listOf(mock())) whenever(getNodeByHandle(any())).thenReturn(mock()) underTest.state.map { it.isInvalidHandle }.distinctUntilChanged() .test { assertThat(awaitItem()).isEqualTo(true) - underTest.setIncomingTreeDepth(any(), 123456789L) + underTest.setIncomingTreeDepth(1, 123456789L) assertThat(awaitItem()).isEqualTo(false) whenever(getNodeByHandle(any())).thenReturn(null) - underTest.setIncomingTreeDepth(any(), 987654321L) + underTest.setIncomingTreeDepth(1, 987654321L) assertThat(awaitItem()).isEqualTo(true) } } From a5ee484d6d8af2fbd1538b4ea702a12c72e6249e Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 23 Jan 2023 16:13:46 +0530 Subject: [PATCH 183/334] AND-15405 Outgoing folder name displayed instead of undecrypted folder --- .../android/app/main/adapters/MegaNodeAdapter.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index e418fc0d1d7..d0890ec4f7e 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1095,7 +1095,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { if (isMandatoryFingerprintVerificationNeeded && !unverifiedIncomingNodeHandles.isEmpty() && unverifiedIncomingNodeHandles.contains(node.getHandle())) { - showUnverifiedNodeUi(holder); + showUnverifiedNodeUi(holder, true); } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { @@ -1108,7 +1108,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { if (isMandatoryFingerprintVerificationNeeded && !unverifiedOutgoingNodeHandles.isEmpty() && unverifiedOutgoingNodeHandles.contains(node.getHandle())) { - showUnverifiedNodeUi(holder); + showUnverifiedNodeUi(holder, false); } } } else { @@ -1548,10 +1548,14 @@ public void setUnverifiedOutgoingNodeHandles(List handles) { /** * Function to show Unverified node UI items accordingly * - * @param holder [ViewHolderBrowserList] + * @param holder [ViewHolderBrowserList] + * @param isIncomingNode boolean to indicate if the node is incoming so that + * "Undecrypted folder" is displayed instead of node name */ - private void showUnverifiedNodeUi(ViewHolderBrowserList holder) { - holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + private void showUnverifiedNodeUi(ViewHolderBrowserList holder, Boolean isIncomingNode) { + if (isIncomingNode) { + holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + } holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); holder.permissionsIcon.setVisibility(View.VISIBLE); holder.permissionsIcon.setImageResource(R.drawable.serious_warning); From d4151d0fb4cb6b087fd6b4b74c4ce1fed450ef1d Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 23 Jan 2023 16:43:18 +0530 Subject: [PATCH 184/334] unnecessary change reverted --- .../app/presentation/shares/incoming/IncomingSharesViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 36e0484a396..a458941ff23 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -95,6 +95,7 @@ class IncomingSharesViewModel @Inject constructor( } } + /** * Refresh incoming shares node */ From dbe52db6855a3b6ab13b94eb7fbc47e268b68c53 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 30 Jan 2023 17:48:33 +0530 Subject: [PATCH 185/334] Add undecrypted files string to the recent items in cloud drive --- .../recent/RecentsBucketViewModel.kt | 10 ++++ .../recentactions/RecentActionsAdapter.kt | 27 +++++++-- .../recentactions/RecentActionsFragment.kt | 42 ++++++++----- app/src/main/res/values/strings.xml | 59 ++++++++++++++++++- .../privacy/android/data/mapper/NodeMapper.kt | 2 + .../data/model/node/DefaultFileNode.kt | 1 + .../data/model/node/DefaultFolderNode.kt | 1 + .../android/domain/entity/node/FileNode.kt | 5 ++ .../android/domain/entity/node/FolderNode.kt | 5 ++ 9 files changed, 129 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt index cdb548195dc..1e5d016e98a 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt @@ -20,6 +20,7 @@ import mega.privacy.android.domain.usecase.UpdateRecentAction import mega.privacy.android.app.fragments.homepage.NodeItem import mega.privacy.android.data.qualifier.MegaApi import mega.privacy.android.domain.entity.RecentActionBucket +import mega.privacy.android.domain.usecase.AreCredentialsVerified import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaNode import timber.log.Timber @@ -35,6 +36,7 @@ class RecentsBucketViewModel @Inject constructor( private val updateRecentAction: UpdateRecentAction, private val getRecentActionNodes: GetRecentActionNodes, monitorNodeUpdates: MonitorNodeUpdates, + private val areCredentialsVerifiedUseCase: AreCredentialsVerified, ) : ViewModel() { private val _actionMode = MutableLiveData() @@ -67,6 +69,10 @@ class RecentsBucketViewModel @Inject constructor( */ val shouldCloseFragment: LiveData = _shouldCloseFragment + private val _areCredentialsVerified: MutableLiveData = MutableLiveData(false) + + val areCredentialsVerified: LiveData = _areCredentialsVerified + /** * True if the parent of the bucket is an incoming shares */ @@ -92,6 +98,10 @@ class RecentsBucketViewModel @Inject constructor( clearSelection() } } + + viewModelScope.launch { + _areCredentialsVerified.postValue(areCredentialsVerifiedUseCase.invoke(megaApi.myUser.email)) + } } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index 63871805bd6..71d39934218 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -72,6 +72,8 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter - // If only one element in the bucket - if (item.bucket.nodes.size == 1) { - lifecycleScope.launch { - val node = item.bucket.nodes[0] - viewModel.getMegaNode(node.id.longValue)?.let { - openFile(position, it) + + if (!item.bucket.nodes[0].isNodeKeyDecrypted && !megaApi.areCredentialsVerified(megaApi.myUser)) { + Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { + putExtra(Constants.EMAIL, item.bucket.userEmail) + requireActivity().startActivity(this) + } + } else { + // If only one element in the bucket + if (item.bucket.nodes.size == 1) { + lifecycleScope.launch { + val node = item.bucket.nodes[0] + viewModel.getMegaNode(node.id.longValue)?.let { + openFile(position, it) + } } } - } - // If more element in the bucket - else { - viewModel.select(item) - val currentDestination = - Navigation.findNavController(requireView()).currentDestination - if (currentDestination != null && currentDestination.id == R.id.homepageFragment) { - Navigation.findNavController(requireView()) - .navigate(HomepageFragmentDirections.actionHomepageToRecentBucket(), - NavOptions.Builder().build()) + // If more element in the bucket + else { + viewModel.select(item) + val currentDestination = + Navigation.findNavController(requireView()).currentDestination + if (currentDestination != null && currentDestination.id == R.id.homepageFragment) { + Navigation.findNavController(requireView()) + .navigate(HomepageFragmentDirections.actionHomepageToRecentBucket(), + NavOptions.Builder().build()) + } } } } @@ -167,6 +176,7 @@ class RecentActionsFragment : Fragment() { listView.addItemDecoration(HeaderItemDecoration(requireContext())) listView.clipToPadding = false listView.itemAnimator = DefaultItemAnimator() + adapter.setIsUserVerified(megaApi.areCredentialsVerified(megaApi.myUser)) } /** diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 87336726d87..db8b404b1b0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -790,7 +790,7 @@ Save - The Recovery key has been successfully copied + Recovery key copied to clipboard. Save it to a safe place where you can easily access later. Change @@ -4138,7 +4138,60 @@ Bonus expires in %1$d day Bonus expires in %1$d days + + Description + + Remove from album? + + To enable camera uploads, grant MEGA access to your photos and other media on your device. + + Grant access + + Don’t grant + + + Removed %d item from “%s” + Removed %d items from “%s” + + + One-off meeting + + Resume video? + + %1$s will resume from %2$s + + Resume + + Restart + + [A]%s [/A][B]invited you to a meeting scheduled for:[/B] + + [A]%s [/A][B]cancelled the meeting scheduled for:[/B] + + [A]%s [/A][B]updated the meeting name from “%s” to [/B]“[A]%s[/A]“ + + [A]%s [/A][B]updated the meeting date[/B] + + [A]%s [/A][B]updated the meeting time[/B] + + [A]%s [/A][B]updated the meeting description[/B] + + [A]%s [/A][B]updated the meeting details scheduled for:[/B] + + Access Denied + + You denied MEGA access to your device’s storage and media files. If you’d like to continue sharing, allow MEGA permission. + + Allow permission + + Don’t allow Security upgrade - Your account’s security is now being upgraded. This will happen only once. If you have seen this message for this account before, press Cancel. - You are currently sharing the following folders: %s + Your account’s security is now being upgraded. This will happen only once. If you’ve seen this message for this account before, tap Cancel. + You’re currently sharing the following folders: %s + + + + [Undecrypted file] + [Undecrypted files] + \ No newline at end of file diff --git a/data/src/main/java/mega/privacy/android/data/mapper/NodeMapper.kt b/data/src/main/java/mega/privacy/android/data/mapper/NodeMapper.kt index e1679c13cab..1f2eb91ab28 100644 --- a/data/src/main/java/mega/privacy/android/data/mapper/NodeMapper.kt +++ b/data/src/main/java/mega/privacy/android/data/mapper/NodeMapper.kt @@ -54,6 +54,7 @@ internal suspend fun toNode( isShared = megaNode.isOutShare, isPendingShare = isPendingShare(megaNode), device = megaNode.deviceId, + isNodeKeyDecrypted = megaNode.isNodeKeyDecrypted, ) } else { DefaultFileNode( @@ -72,5 +73,6 @@ internal suspend fun toNode( isTakenDown = megaNode.isTakenDown, isIncomingShare = megaNode.isInShare, fingerprint = megaNode.fingerprint, + isNodeKeyDecrypted = megaNode.isNodeKeyDecrypted, ) } diff --git a/data/src/main/java/mega/privacy/android/data/model/node/DefaultFileNode.kt b/data/src/main/java/mega/privacy/android/data/model/node/DefaultFileNode.kt index c489315acfe..dbaceab357b 100644 --- a/data/src/main/java/mega/privacy/android/data/model/node/DefaultFileNode.kt +++ b/data/src/main/java/mega/privacy/android/data/model/node/DefaultFileNode.kt @@ -20,4 +20,5 @@ internal data class DefaultFileNode( override val isTakenDown: Boolean, override val isIncomingShare: Boolean, override val fingerprint: String?, + override val isNodeKeyDecrypted: Boolean, ) : FileNode diff --git a/data/src/main/java/mega/privacy/android/data/model/node/DefaultFolderNode.kt b/data/src/main/java/mega/privacy/android/data/model/node/DefaultFolderNode.kt index cca0c4757da..348a9f4e92a 100644 --- a/data/src/main/java/mega/privacy/android/data/model/node/DefaultFolderNode.kt +++ b/data/src/main/java/mega/privacy/android/data/model/node/DefaultFolderNode.kt @@ -20,4 +20,5 @@ internal data class DefaultFolderNode( override val isShared: Boolean, override val isPendingShare: Boolean, override val device: String?, + override val isNodeKeyDecrypted: Boolean, ) : FolderNode \ No newline at end of file diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FileNode.kt b/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FileNode.kt index 4dff47573d9..d0c6efa4eca 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FileNode.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FileNode.kt @@ -30,4 +30,9 @@ interface FileNode : UnTypedNode { * Fingerprint */ val fingerprint: String? + + /** + * Is node key decrypted by verification from owner + */ + val isNodeKeyDecrypted: Boolean } diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FolderNode.kt b/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FolderNode.kt index 7954ef08f91..b556cf694d5 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FolderNode.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/entity/node/FolderNode.kt @@ -34,4 +34,9 @@ interface FolderNode : UnTypedNode { * Number of child files */ val childFileCount: Int + + /** + * Is node key decrypted by verification from owner + */ + val isNodeKeyDecrypted: Boolean } From d69d2e69777458181bb97e797eca0295717c0262 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 30 Jan 2023 21:15:56 +0530 Subject: [PATCH 186/334] Resolve Code comments Contact verification screen opened on Unverified incoming node tap action NO_KEY name for Unverified incoming node fixed Code optimised to use viewmodel --- .../recent/RecentsBucketViewModel.kt | 9 +--- .../app/main/adapters/MegaNodeAdapter.java | 4 +- .../NodeOptionsBottomSheetDialogFragment.java | 6 ++- .../recentactions/RecentActionsAdapter.kt | 2 +- .../recentactions/RecentActionsFragment.kt | 10 +++- .../recentactions/RecentActionsViewModel.kt | 13 ++++- .../recentactions/model/RecentActionsState.kt | 2 + .../shares/incoming/IncomingSharesFragment.kt | 50 ++++++++++++------- .../RecentActionsViewModelTest.kt | 4 ++ 9 files changed, 67 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt index 1e5d016e98a..8199086b13a 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt @@ -36,7 +36,6 @@ class RecentsBucketViewModel @Inject constructor( private val updateRecentAction: UpdateRecentAction, private val getRecentActionNodes: GetRecentActionNodes, monitorNodeUpdates: MonitorNodeUpdates, - private val areCredentialsVerifiedUseCase: AreCredentialsVerified, ) : ViewModel() { private val _actionMode = MutableLiveData() @@ -69,9 +68,7 @@ class RecentsBucketViewModel @Inject constructor( */ val shouldCloseFragment: LiveData = _shouldCloseFragment - private val _areCredentialsVerified: MutableLiveData = MutableLiveData(false) - - val areCredentialsVerified: LiveData = _areCredentialsVerified + private val _areCredentialsVerified: MutableStateFlow = MutableStateFlow(false) /** * True if the parent of the bucket is an incoming shares @@ -98,10 +95,6 @@ class RecentsBucketViewModel @Inject constructor( clearSelection() } } - - viewModelScope.launch { - _areCredentialsVerified.postValue(areCredentialsVerifiedUseCase.invoke(megaApi.myUser.email)) - } } /** diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index d0890ec4f7e..23d08a57a77 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1093,8 +1093,8 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } if (isMandatoryFingerprintVerificationNeeded - && !unverifiedIncomingNodeHandles.isEmpty() - && unverifiedIncomingNodeHandles.contains(node.getHandle())) { + && !node.isNodeKeyDecrypted() + && !megaApi.areCredentialsVerified(megaApi.getMyUser())) { showUnverifiedNodeUi(holder, true); } holder.permissionsIcon.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 4dba22ed35c..48bd00b5606 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -728,7 +728,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat ViewExtensionsKt.collectFlow(requireActivity(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE - && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { + && isIncomingNodeVerified()) { setUnverifiedNodeUserName(state.getUnverifiedIncomingShares()); hideNodeActions(); } @@ -924,6 +924,10 @@ private boolean isNodeUnverified(List shareDataList) { return shareDataList.contains(node.getHandle()); } + private boolean isIncomingNodeVerified() { + return !node.isNodeKeyDecrypted() && !megaApi.areCredentialsVerified(megaApi.getMyUser()); + } + @SuppressLint("NonConstantResourceId") @Override public void onClick(View v) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index 71d39934218..ff5e41f53fd 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -354,7 +354,7 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter - if (!item.bucket.nodes[0].isNodeKeyDecrypted && !megaApi.areCredentialsVerified(megaApi.myUser)) { + if (!item.bucket.nodes[0].isNodeKeyDecrypted && !viewModel.state.value.areUserCredentialsVerified) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, item.bucket.userEmail) requireActivity().startActivity(this) @@ -176,7 +176,13 @@ class RecentActionsFragment : Fragment() { listView.addItemDecoration(HeaderItemDecoration(requireContext())) listView.clipToPadding = false listView.itemAnimator = DefaultItemAnimator() - adapter.setIsUserVerified(megaApi.areCredentialsVerified(megaApi.myUser)) + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + viewModel.state.collect { + adapter.setAreUserCredentialsVerified(it.areUserCredentialsVerified) + } + } + } } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index 4d5d34e5950..c4dd65a0b11 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -11,7 +11,6 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetParentMegaNode -import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.app.domain.usecase.IsPendingShare import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates import mega.privacy.android.app.presentation.recentactions.model.RecentActionItemType @@ -19,7 +18,9 @@ import mega.privacy.android.app.presentation.recentactions.model.RecentActionsSh import mega.privacy.android.app.presentation.recentactions.model.RecentActionsState import mega.privacy.android.domain.entity.RecentActionBucket import mega.privacy.android.domain.entity.contacts.ContactItem +import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails +import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts import mega.privacy.android.domain.usecase.MonitorHideRecentActivity import mega.privacy.android.domain.usecase.SetHideRecentActivity @@ -35,6 +36,7 @@ import javax.inject.Inject * @param getVisibleContacts * @param setHideRecentActivity * @param monitorNodeUpdates + * @param areCredentialsVerified */ @HiltViewModel class RecentActionsViewModel @Inject constructor( @@ -47,6 +49,7 @@ class RecentActionsViewModel @Inject constructor( private val getParentMegaNode: GetParentMegaNode, monitorHideRecentActivity: MonitorHideRecentActivity, monitorNodeUpdates: MonitorNodeUpdates, + val areCredentialsVerified: AreCredentialsVerified, ) : ViewModel() { private var _buckets = listOf() @@ -84,6 +87,14 @@ class RecentActionsViewModel @Inject constructor( } } + fun checkIfUserCredentialsAreVerified(userEmail: String) { + viewModelScope.launch { + _state.update { + it.copy(areUserCredentialsVerified = areCredentialsVerified(userEmail)) + } + } + } + /** * Set the selected recent actions bucket and current recent actions bucket list * diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt index d0bd2cfce93..6a3f712ef5f 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt @@ -5,8 +5,10 @@ package mega.privacy.android.app.presentation.recentactions.model * * @param recentActionItems list of recent action items * @param hideRecentActivity true if recent activity should be hidden + * @param areUserCredentialsVerified true if user credentials are verified from Mega Api */ data class RecentActionsState( val recentActionItems: List = emptyList(), val hideRecentActivity: Boolean = false, + val areUserCredentialsVerified: Boolean = false, ) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 6bcc1f19128..c82a1a60c51 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -1,5 +1,6 @@ package mega.privacy.android.app.presentation.shares.incoming +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.Menu @@ -17,6 +18,7 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.R import mega.privacy.android.app.components.NewGridRecyclerView import mega.privacy.android.app.main.adapters.MegaNodeAdapter +import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.manager.model.Tab import mega.privacy.android.app.presentation.shares.MegaNodeBaseFragment @@ -26,6 +28,7 @@ import mega.privacy.android.app.utils.ColorUtils.setImageViewAlphaIfDark import mega.privacy.android.app.utils.Constants import mega.privacy.android.app.utils.Constants.ORDER_CLOUD import mega.privacy.android.app.utils.Constants.ORDER_OTHERS +import mega.privacy.android.app.utils.ContactUtil import mega.privacy.android.app.utils.MegaNodeUtil.allHaveFullAccess import mega.privacy.android.app.utils.MegaNodeUtil.areAllFileNodesAndNotTakenDown import mega.privacy.android.app.utils.MegaNodeUtil.areAllNotTakenDown @@ -95,27 +98,38 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { override fun itemClick(position: Int) { val actualPosition = position - 1 - when { - // select mode - adapter?.isMultipleSelect == true -> { - adapter?.toggleSelection(position) - val selectedNodes = adapter?.selectedNodes - if ((selectedNodes?.size ?: 0) > 0) - updateActionModeTitle() + if (!state().nodes[actualPosition].isNodeKeyDecrypted && + !megaApi.areCredentialsVerified(megaApi.myUser) + ) { + Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { + putExtra(Constants.EMAIL, + ContactUtil.getContactEmailDB(state().nodes[actualPosition].owner)) + requireActivity().startActivity(this) } + } else { + when { + // select mode + adapter?.isMultipleSelect == true -> { + adapter?.toggleSelection(position) + val selectedNodes = adapter?.selectedNodes + if ((selectedNodes?.size ?: 0) > 0) + updateActionModeTitle() + } - // click on a folder - state().nodes[actualPosition].isFolder -> - navigateToFolder(state().nodes[actualPosition]) - - // click on a file - else -> - openFile( - state().nodes[actualPosition], - Constants.INCOMING_SHARES_ADAPTER, - actualPosition - ) + // click on a folder + state().nodes[actualPosition].isFolder -> + navigateToFolder(state().nodes[actualPosition]) + + // click on a file + else -> + openFile( + state().nodes[actualPosition], + Constants.INCOMING_SHARES_ADAPTER, + actualPosition + ) + } } + } override fun navigateToFolder(node: MegaNode) { diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt index 990cd56ca10..0c374150173 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt @@ -24,6 +24,7 @@ import mega.privacy.android.domain.entity.contacts.ContactItem import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.entity.node.TypedFileNode +import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -69,6 +70,8 @@ class RecentActionsViewModelTest { } private val monitorNodeUpdates = FakeMonitorUpdates() + private val areCredentialsVerified = mock() + private val node: TypedFileNode = mock { on { id }.thenReturn(NodeId(123)) } @@ -114,6 +117,7 @@ class RecentActionsViewModelTest { getParentMegaNode, monitorHideRecentActivity, monitorNodeUpdates, + areCredentialsVerified ) } From 3ad44baf4f193be19b22c868c0ff113a3bedf569 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 30 Jan 2023 21:44:49 +0530 Subject: [PATCH 187/334] Test failure fixed --- .../data/repository/DefaultMediaPlayerRepositoryTest.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/data/src/test/java/mega/privacy/android/data/repository/DefaultMediaPlayerRepositoryTest.kt b/data/src/test/java/mega/privacy/android/data/repository/DefaultMediaPlayerRepositoryTest.kt index 86c692547f2..2f6f65ab1ff 100644 --- a/data/src/test/java/mega/privacy/android/data/repository/DefaultMediaPlayerRepositoryTest.kt +++ b/data/src/test/java/mega/privacy/android/data/repository/DefaultMediaPlayerRepositoryTest.kt @@ -73,7 +73,7 @@ class DefaultMediaPlayerRepositoryTest { private val expectedType = StaticImageFileTypeInfo(mimeType = "", extension = "image") private val expectedFileMegaNode = createMegaNode(false) private val expectedFolderMegaNode = createMegaNode(true) - + private val isNodeKetDecrypted = false private val expectedMediaId: Long = 1234567 private val expectedTotalDuration: Long = 200000 private val expectedCurrentPosition: Long = 16000 @@ -290,7 +290,8 @@ class DefaultMediaPlayerRepositoryTest { isIncomingShare = expectedIncomingShare, isShared = expectedInShared, isPendingShare = expectedIsPendingShare, - device = expectedDevice + device = expectedDevice, + isNodeKeyDecrypted = isNodeKetDecrypted, ) private fun createTypedFileNode() = DefaultFileNode( @@ -308,7 +309,8 @@ class DefaultMediaPlayerRepositoryTest { modificationTime = expectedModificationTime, fingerprint = expectedFingerprint, thumbnailPath = expectedThumbnailPath, - type = expectedType + type = expectedType, + isNodeKeyDecrypted = isNodeKetDecrypted, ) private suspend fun initTestConditions(megaNode: MegaNode, typeInfo: FileTypeInfo) { From 98dfeb457395c63f2a28921fac11e1df46ce7ec9 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 31 Jan 2023 11:43:41 +0530 Subject: [PATCH 188/334] Code comments fixed. --- .../app/fragments/recent/RecentsBucketViewModel.kt | 3 --- .../android/app/main/adapters/MegaNodeAdapter.java | 8 ++++---- .../NodeOptionsBottomSheetDialogFragment.java | 2 +- .../recentactions/RecentActionsFragment.kt | 4 ++-- .../recentactions/RecentActionsViewModel.kt | 11 ----------- .../recentactions/model/RecentActionsState.kt | 2 -- .../shares/incoming/IncomingSharesFragment.kt | 2 +- .../recentactions/RecentActionsViewModelTest.kt | 3 --- 8 files changed, 8 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt index 8199086b13a..cdb548195dc 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketViewModel.kt @@ -20,7 +20,6 @@ import mega.privacy.android.domain.usecase.UpdateRecentAction import mega.privacy.android.app.fragments.homepage.NodeItem import mega.privacy.android.data.qualifier.MegaApi import mega.privacy.android.domain.entity.RecentActionBucket -import mega.privacy.android.domain.usecase.AreCredentialsVerified import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaNode import timber.log.Timber @@ -68,8 +67,6 @@ class RecentsBucketViewModel @Inject constructor( */ val shouldCloseFragment: LiveData = _shouldCloseFragment - private val _areCredentialsVerified: MutableStateFlow = MutableStateFlow(false) - /** * True if the parent of the bucket is an incoming shares */ diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 23d08a57a77..9c9fc9c335a 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1092,10 +1092,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if (isMandatoryFingerprintVerificationNeeded - && !node.isNodeKeyDecrypted() - && !megaApi.areCredentialsVerified(megaApi.getMyUser())) { - showUnverifiedNodeUi(holder, true); + if (isMandatoryFingerprintVerificationNeeded) { + if (!node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getMyUser())) { + showUnverifiedNodeUi(holder, true); + } } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 48bd00b5606..d3188607ad5 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -925,7 +925,7 @@ private boolean isNodeUnverified(List shareDataList) { } private boolean isIncomingNodeVerified() { - return !node.isNodeKeyDecrypted() && !megaApi.areCredentialsVerified(megaApi.getMyUser()); + return !node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getMyUser()); } @SuppressLint("NonConstantResourceId") diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt index 6432d311599..9797d6d253b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt @@ -130,7 +130,7 @@ class RecentActionsFragment : Fragment() { private fun initAdapter() { adapter.setOnItemClickListener { item, position -> - if (!item.bucket.nodes[0].isNodeKeyDecrypted && !viewModel.state.value.areUserCredentialsVerified) { + if (!item.bucket.nodes[0].isNodeKeyDecrypted || !megaApi.areCredentialsVerified(megaApi.myUser)) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, item.bucket.userEmail) requireActivity().startActivity(this) @@ -179,7 +179,7 @@ class RecentActionsFragment : Fragment() { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { viewModel.state.collect { - adapter.setAreUserCredentialsVerified(it.areUserCredentialsVerified) + adapter.setAreUserCredentialsVerified(megaApi.areCredentialsVerified(megaApi.myUser)) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index c4dd65a0b11..b54585fb3c0 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -18,7 +18,6 @@ import mega.privacy.android.app.presentation.recentactions.model.RecentActionsSh import mega.privacy.android.app.presentation.recentactions.model.RecentActionsState import mega.privacy.android.domain.entity.RecentActionBucket import mega.privacy.android.domain.entity.contacts.ContactItem -import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -36,7 +35,6 @@ import javax.inject.Inject * @param getVisibleContacts * @param setHideRecentActivity * @param monitorNodeUpdates - * @param areCredentialsVerified */ @HiltViewModel class RecentActionsViewModel @Inject constructor( @@ -49,7 +47,6 @@ class RecentActionsViewModel @Inject constructor( private val getParentMegaNode: GetParentMegaNode, monitorHideRecentActivity: MonitorHideRecentActivity, monitorNodeUpdates: MonitorNodeUpdates, - val areCredentialsVerified: AreCredentialsVerified, ) : ViewModel() { private var _buckets = listOf() @@ -87,14 +84,6 @@ class RecentActionsViewModel @Inject constructor( } } - fun checkIfUserCredentialsAreVerified(userEmail: String) { - viewModelScope.launch { - _state.update { - it.copy(areUserCredentialsVerified = areCredentialsVerified(userEmail)) - } - } - } - /** * Set the selected recent actions bucket and current recent actions bucket list * diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt index 6a3f712ef5f..d0bd2cfce93 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionsState.kt @@ -5,10 +5,8 @@ package mega.privacy.android.app.presentation.recentactions.model * * @param recentActionItems list of recent action items * @param hideRecentActivity true if recent activity should be hidden - * @param areUserCredentialsVerified true if user credentials are verified from Mega Api */ data class RecentActionsState( val recentActionItems: List = emptyList(), val hideRecentActivity: Boolean = false, - val areUserCredentialsVerified: Boolean = false, ) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index c82a1a60c51..1d472c30e11 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -98,7 +98,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { override fun itemClick(position: Int) { val actualPosition = position - 1 - if (!state().nodes[actualPosition].isNodeKeyDecrypted && + if (!state().nodes[actualPosition].isNodeKeyDecrypted || !megaApi.areCredentialsVerified(megaApi.myUser) ) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt index 0c374150173..7e63350e1a3 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt @@ -70,8 +70,6 @@ class RecentActionsViewModelTest { } private val monitorNodeUpdates = FakeMonitorUpdates() - private val areCredentialsVerified = mock() - private val node: TypedFileNode = mock { on { id }.thenReturn(NodeId(123)) } @@ -117,7 +115,6 @@ class RecentActionsViewModelTest { getParentMegaNode, monitorHideRecentActivity, monitorNodeUpdates, - areCredentialsVerified ) } From b2f1e150142a9fbb198368b7d6fdb9569d518017 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 31 Jan 2023 13:28:02 +0530 Subject: [PATCH 189/334] areCredentialsVerified checked with node owner --- .../app/main/adapters/MegaNodeAdapter.java | 3 +- .../NodeOptionsBottomSheetDialogFragment.java | 2 +- .../recentactions/RecentActionsAdapter.kt | 2 +- .../recentactions/RecentActionsFragment.kt | 31 ++++++++++++++----- .../shares/incoming/IncomingSharesFragment.kt | 2 +- 5 files changed, 28 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 9c9fc9c335a..7240539d999 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -97,6 +97,7 @@ import mega.privacy.android.app.presentation.shares.links.LinksFragment; import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesFragment; import mega.privacy.android.app.utils.ColorUtils; +import mega.privacy.android.app.utils.ContactUtil; import mega.privacy.android.app.utils.MegaNodeUtil; import mega.privacy.android.app.utils.NodeTakenDownDialogListener; import mega.privacy.android.app.utils.ThumbnailUtils; @@ -1093,7 +1094,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } if (isMandatoryFingerprintVerificationNeeded) { - if (!node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getMyUser())) { + if (!node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getContact(String.valueOf(node.getOwner())))) { showUnverifiedNodeUi(holder, true); } } diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index d3188607ad5..1d455f51356 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -925,7 +925,7 @@ private boolean isNodeUnverified(List shareDataList) { } private boolean isIncomingNodeVerified() { - return !node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getMyUser()); + return !node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getContact(String.valueOf(node.getOwner()))); } @SuppressLint("NonConstantResourceId") diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index ff5e41f53fd..64e47e8b56a 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -193,7 +193,7 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter - if (!item.bucket.nodes[0].isNodeKeyDecrypted || !megaApi.areCredentialsVerified(megaApi.myUser)) { + if (!item.bucket.nodes[0].isNodeKeyDecrypted || + !megaApi.areCredentialsVerified(megaApi.getContact(item.bucket.nodes[0].id.longValue.toString())) + ) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, item.bucket.userEmail) requireActivity().startActivity(this) @@ -176,13 +179,6 @@ class RecentActionsFragment : Fragment() { listView.addItemDecoration(HeaderItemDecoration(requireContext())) listView.clipToPadding = false listView.itemAnimator = DefaultItemAnimator() - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - viewModel.state.collect { - adapter.setAreUserCredentialsVerified(megaApi.areCredentialsVerified(megaApi.myUser)) - } - } - } } /** @@ -192,10 +188,29 @@ class RecentActionsFragment : Fragment() { */ private fun setRecentActions(recentActionItems: List) { adapter.setItems(recentActionItems) + adapter.setAreUserCredentialsVerified( + megaApi.areCredentialsVerified( + megaApi.getContact(getUserEmail(recentActionItems)) + ) + ) listView.layoutManager = TopSnappedStickyLayoutManager(requireContext()) { recentActionItems } } + /** + * Function to get user email from the adapter data set only if it contains bucket otherwise returns blank + * + * @param recentActionItems List of [RecentActionItemType] which is provided to adapter + */ + private fun getUserEmail(recentActionItems: List): String { + for (itemType: RecentActionItemType in recentActionItems) { + if (itemType is RecentActionItemType.Item) { + return itemType.bucket.userEmail + } + } + return "" + } + /** * Display the recent actions activity. * Hide the activity if the setting to hide is enabled, and shows it if the diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 1d472c30e11..9df3393bb35 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -99,7 +99,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { val actualPosition = position - 1 if (!state().nodes[actualPosition].isNodeKeyDecrypted || - !megaApi.areCredentialsVerified(megaApi.myUser) + !megaApi.areCredentialsVerified(megaApi.getContact(state().nodes[actualPosition].owner.toString())) ) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, From 8a0379df7c546b6dbe122c5f248768b7d7e64e43 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 31 Jan 2023 16:29:06 +0530 Subject: [PATCH 190/334] Code comments fixed --- .../recentactions/RecentActionsAdapter.kt | 11 +------ .../recentactions/RecentActionsFragment.kt | 6 ---- .../recentactions/RecentActionsViewModel.kt | 29 ++++++++++++++----- .../model/RecentActionItemType.kt | 1 + .../RecentActionsViewModelTest.kt | 3 ++ 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index 64e47e8b56a..5b7003d82a6 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -193,7 +193,7 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter) { adapter.setItems(recentActionItems) - adapter.setAreUserCredentialsVerified( - megaApi.areCredentialsVerified( - megaApi.getContact(getUserEmail(recentActionItems)) - ) - ) listView.layoutManager = TopSnappedStickyLayoutManager(requireContext()) { recentActionItems } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index b54585fb3c0..db4584343c7 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -18,6 +18,7 @@ import mega.privacy.android.app.presentation.recentactions.model.RecentActionsSh import mega.privacy.android.app.presentation.recentactions.model.RecentActionsState import mega.privacy.android.domain.entity.RecentActionBucket import mega.privacy.android.domain.entity.contacts.ContactItem +import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -35,6 +36,7 @@ import javax.inject.Inject * @param getVisibleContacts * @param setHideRecentActivity * @param monitorNodeUpdates + * @param areCredentialsVerified */ @HiltViewModel class RecentActionsViewModel @Inject constructor( @@ -47,6 +49,7 @@ class RecentActionsViewModel @Inject constructor( private val getParentMegaNode: GetParentMegaNode, monitorHideRecentActivity: MonitorHideRecentActivity, monitorNodeUpdates: MonitorNodeUpdates, + val areCredentialsVerified: AreCredentialsVerified, ) : ViewModel() { private var _buckets = listOf() @@ -164,14 +167,24 @@ class RecentActionsViewModel @Inject constructor( val parentNode = getNodeByHandle(bucket.parentHandle) val sharesType = getParentSharesType(parentNode) - - recentItemList.add(RecentActionItemType.Item( - bucket, - userName, - parentNode?.name ?: "", - sharesType, - currentUserIsOwner, - )) + if (!bucket.nodes[0].isNodeKeyDecrypted) { + recentItemList.add(RecentActionItemType.Item( + bucket, + userName, + parentNode?.name ?: "", + sharesType, + currentUserIsOwner, + areCredentialsVerified(bucket.userEmail), + )) + } else { + recentItemList.add(RecentActionItemType.Item( + bucket, + userName, + parentNode?.name ?: "", + sharesType, + currentUserIsOwner, + )) + } } return recentItemList diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt index 4c47f4ded64..724701cf1e8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt @@ -26,6 +26,7 @@ sealed class RecentActionItemType(val timestamp: Long) { val parentFolderName: String = "", val parentFolderSharesType: RecentActionsSharesType = RecentActionsSharesType.NONE, val currentUserIsOwner: Boolean = false, + val areCredentialsVerified: Boolean = false, ) : RecentActionItemType(timestamp = bucket.timestamp) /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt index 7e63350e1a3..56736674490 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt @@ -70,6 +70,8 @@ class RecentActionsViewModelTest { } private val monitorNodeUpdates = FakeMonitorUpdates() + private val areCredentialsVerified = mock() + private val node: TypedFileNode = mock { on { id }.thenReturn(NodeId(123)) } @@ -115,6 +117,7 @@ class RecentActionsViewModelTest { getParentMegaNode, monitorHideRecentActivity, monitorNodeUpdates, + areCredentialsVerified, ) } From aff1f06a4f1fdfe5ac3fdef931fee57cc44613e4 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 31 Jan 2023 19:31:26 +0530 Subject: [PATCH 191/334] Failing test case fixed --- .../presentation/recentactions/RecentActionsViewModel.kt | 3 ++- .../recentactions/RecentActionsViewModelTest.kt | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index db4584343c7..721a445d736 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -167,6 +167,7 @@ class RecentActionsViewModel @Inject constructor( val parentNode = getNodeByHandle(bucket.parentHandle) val sharesType = getParentSharesType(parentNode) + val areUserCredentialsVerified = areCredentialsVerified(bucket.userEmail) if (!bucket.nodes[0].isNodeKeyDecrypted) { recentItemList.add(RecentActionItemType.Item( bucket, @@ -174,7 +175,7 @@ class RecentActionsViewModel @Inject constructor( parentNode?.name ?: "", sharesType, currentUserIsOwner, - areCredentialsVerified(bucket.userEmail), + areUserCredentialsVerified, )) } else { recentItemList.add(RecentActionItemType.Item( diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt index 56736674490..486e1afe3c2 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt @@ -70,10 +70,13 @@ class RecentActionsViewModelTest { } private val monitorNodeUpdates = FakeMonitorUpdates() - private val areCredentialsVerified = mock() + private val areCredentialsVerified = mock { + onBlocking { invoke(any()) }.thenReturn(false) + } private val node: TypedFileNode = mock { on { id }.thenReturn(NodeId(123)) + on { isNodeKeyDecrypted }.thenReturn(false) } private val megaRecentActionBucket = mock { @@ -300,6 +303,7 @@ class RecentActionsViewModelTest { val expected = "Cloud drive" val parentNode = mock { on { name }.thenReturn(expected) + on { isNodeKeyDecrypted }.thenReturn(false) } whenever(getRecentActions()).thenReturn(listOf(megaRecentActionBucket)) whenever(getNodeByHandle(any())).thenReturn(parentNode) @@ -372,7 +376,7 @@ class RecentActionsViewModelTest { on { isOutShare }.thenReturn(false) } whenever(getRecentActions()).thenReturn(listOf(megaRecentActionBucket)) - whenever(getNodeByHandle(any())).thenReturn(parentNode) + whenever(getNodeByHandle(1)).thenReturn(parentNode) whenever(isPendingShare(parentNode.handle)).thenReturn(false) whenever(getParentMegaNode(parentNode)).thenReturn(null) underTest.state.map { it.recentActionItems }.distinctUntilChanged() From 538311358e2062bfc9aa921bb13c03d5d2d0ae29 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 1 Feb 2023 09:47:08 +0530 Subject: [PATCH 192/334] Code comments resolved --- .../recentactions/RecentActionsAdapter.kt | 4 +-- .../recentactions/RecentActionsFragment.kt | 18 +----------- .../recentactions/RecentActionsViewModel.kt | 29 +++++++------------ .../model/RecentActionItemType.kt | 3 +- 4 files changed, 14 insertions(+), 40 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index 5b7003d82a6..b6f5288338c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -72,8 +72,6 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter - if (!item.bucket.nodes[0].isNodeKeyDecrypted || - !megaApi.areCredentialsVerified(megaApi.getContact(item.bucket.nodes[0].id.longValue.toString())) - ) { + if (!item.isKeyVerified) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, item.bucket.userEmail) requireActivity().startActivity(this) @@ -191,20 +189,6 @@ class RecentActionsFragment : Fragment() { TopSnappedStickyLayoutManager(requireContext()) { recentActionItems } } - /** - * Function to get user email from the adapter data set only if it contains bucket otherwise returns blank - * - * @param recentActionItems List of [RecentActionItemType] which is provided to adapter - */ - private fun getUserEmail(recentActionItems: List): String { - for (itemType: RecentActionItemType in recentActionItems) { - if (itemType is RecentActionItemType.Item) { - return itemType.bucket.userEmail - } - } - return "" - } - /** * Display the recent actions activity. * Hide the activity if the setting to hide is enabled, and shows it if the diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index 721a445d736..f7e5a28403c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -167,25 +167,16 @@ class RecentActionsViewModel @Inject constructor( val parentNode = getNodeByHandle(bucket.parentHandle) val sharesType = getParentSharesType(parentNode) - val areUserCredentialsVerified = areCredentialsVerified(bucket.userEmail) - if (!bucket.nodes[0].isNodeKeyDecrypted) { - recentItemList.add(RecentActionItemType.Item( - bucket, - userName, - parentNode?.name ?: "", - sharesType, - currentUserIsOwner, - areUserCredentialsVerified, - )) - } else { - recentItemList.add(RecentActionItemType.Item( - bucket, - userName, - parentNode?.name ?: "", - sharesType, - currentUserIsOwner, - )) - } + val isNodeKeyDecrypted = + bucket.nodes[0].isNodeKeyDecrypted && areCredentialsVerified(bucket.userEmail) + recentItemList.add(RecentActionItemType.Item( + bucket, + userName, + parentNode?.name ?: "", + sharesType, + currentUserIsOwner, + isNodeKeyDecrypted, + )) } return recentItemList diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt index 724701cf1e8..c79d3b2cb5b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/model/RecentActionItemType.kt @@ -19,6 +19,7 @@ sealed class RecentActionItemType(val timestamp: Long) { * @property parentFolderName the name of the parent folder containing the nodes * @property parentFolderSharesType the share type of the parent folder * @property currentUserIsOwner true if the current user is the owner of the recent actions + * @property isKeyVerified true if node.isNodeKeyDecrypted & areCredentialsVerified returns true */ class Item( val bucket: RecentActionBucket, @@ -26,7 +27,7 @@ sealed class RecentActionItemType(val timestamp: Long) { val parentFolderName: String = "", val parentFolderSharesType: RecentActionsSharesType = RecentActionsSharesType.NONE, val currentUserIsOwner: Boolean = false, - val areCredentialsVerified: Boolean = false, + val isKeyVerified: Boolean = false, ) : RecentActionItemType(timestamp = bucket.timestamp) /** From ac48e981f0f364e14c40ccef65d04ca26d17be30 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 2 Feb 2023 17:56:31 +1300 Subject: [PATCH 193/334] Added secure flag switch on TourFragment --- .../mega/privacy/android/app/BaseActivity.kt | 1 - .../android/app/featuretoggle/AppFeatures.kt | 6 +++++ .../privacy/android/app/main/TourFragment.kt | 23 +++++++++++++++++-- .../assets/featuretoggle/feature_flags.json | 8 +++++++ 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt index b01ff13255c..10e01a9bd78 100644 --- a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt @@ -463,7 +463,6 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - megaApi.setSecureFlag(true) nameCollisionActivityContract = registerForActivityResult(NameCollisionActivityContract()) { result: String? -> if (result != null) { diff --git a/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt b/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt index b31c2289c0a..c134804cb88 100644 --- a/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt +++ b/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt @@ -20,6 +20,12 @@ enum class AppFeatures(override val description: String, private val defaultValu AndroidSync("Enable a synchronization between folders on local storage and folders on MEGA cloud", false), + + /** + * Sets the MegaApi::setSecureFlag + */ + SetSecureFlag("Sets the secure flag value for MegaApi", false), + /** * Indicates if the user is cryptographically secure */ diff --git a/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt b/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt index e869f9feb99..47b6ed4273b 100644 --- a/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt @@ -20,25 +20,35 @@ import androidx.core.content.ContextCompat import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope import androidx.viewpager.widget.ViewPager import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch import mega.privacy.android.app.BaseActivity +import mega.privacy.android.app.MegaApplication import mega.privacy.android.app.MegaApplication.Companion.isLoggingOut import mega.privacy.android.app.R import mega.privacy.android.app.TourImageAdapter import mega.privacy.android.app.constants.IntentConstants import mega.privacy.android.app.databinding.DialogRecoveryKeyBinding import mega.privacy.android.app.databinding.FragmentTourBinding +import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.meeting.fragments.PasteMeetingLinkGuestDialogFragment import mega.privacy.android.app.presentation.login.LoginActivity import mega.privacy.android.app.utils.Constants import mega.privacy.android.app.utils.permission.PermissionUtils.hasPermissions +import mega.privacy.android.data.qualifier.MegaApi +import mega.privacy.android.domain.usecase.GetFeatureFlagValue +import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaApiJava import timber.log.Timber +import javax.inject.Inject /** * Tour Fragment. */ +@AndroidEntryPoint class TourFragment : Fragment() { private var _binding: FragmentTourBinding? = null @@ -47,6 +57,13 @@ class TourFragment : Fragment() { private lateinit var joinMeetingAsGuestLauncher: ActivityResultLauncher + @Inject + @MegaApi + lateinit var megaApi: MegaApiAndroid + + @Inject + lateinit var getFeatureFlagValue: GetFeatureFlagValue + private val selectedCircle by lazy { ContextCompat.getDrawable(requireContext(), R.drawable.selection_circle_page_adapter) } @@ -81,9 +98,7 @@ class TourFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupView() - arguments?.getString(EXTRA_RECOVERY_KEY_URL, null)?.let { recoveryKeyUrl -> Timber.d("Link to resetPass: $recoveryKeyUrl") showRecoveryKeyDialog(recoveryKeyUrl) @@ -139,6 +154,10 @@ class TourFragment : Fragment() { } }) } + + viewLifecycleOwner.lifecycleScope.launch { + megaApi.setSecureFlag(getFeatureFlagValue(AppFeatures.SetSecureFlag)) + } } /** diff --git a/app/src/qa/assets/featuretoggle/feature_flags.json b/app/src/qa/assets/featuretoggle/feature_flags.json index dd9c6fb6fe3..57ca03d12da 100644 --- a/app/src/qa/assets/featuretoggle/feature_flags.json +++ b/app/src/qa/assets/featuretoggle/feature_flags.json @@ -2,5 +2,13 @@ { "name": "PermanentLogging", "value": true + }, + { + "name": "MandatoryFingerprintVerification", + "value": true + }, + { + "name": "SetSecureFlag", + "value": true } ] \ No newline at end of file From 2a097e089950d96f465cb0a9bb6b618ad48d77c4 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 3 Feb 2023 17:22:01 +0530 Subject: [PATCH 194/334] Undecrypted file node UI bug fixed --- .../app/main/adapters/MegaNodeAdapter.java | 8 +++---- .../NodeOptionsBottomSheetDialogFragment.java | 24 ++++++++++++++----- .../recentactions/RecentActionsAdapter.kt | 15 +++++++++++- .../recentactions/RecentActionsFragment.kt | 24 ++++++++++++++++++- .../recentactions/RecentActionsViewModel.kt | 2 +- .../shares/incoming/IncomingSharesFragment.kt | 5 +--- .../assets/featuretoggle/feature_flags.json | 2 +- 7 files changed, 62 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 7240539d999..c8626a330e0 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1093,10 +1093,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - if (isMandatoryFingerprintVerificationNeeded) { - if (!node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getContact(String.valueOf(node.getOwner())))) { - showUnverifiedNodeUi(holder, true); - } + if (isMandatoryFingerprintVerificationNeeded + && !unverifiedIncomingNodeHandles.isEmpty() + && unverifiedIncomingNodeHandles.contains(node.getHandle())) { + showUnverifiedNodeUi(holder, true); } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 1d455f51356..e34f2a07616 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -728,7 +728,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat ViewExtensionsKt.collectFlow(requireActivity(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE - && isIncomingNodeVerified()) { + && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedIncomingShares()); hideNodeActions(); } @@ -924,10 +924,6 @@ private boolean isNodeUnverified(List shareDataList) { return shareDataList.contains(node.getHandle()); } - private boolean isIncomingNodeVerified() { - return !node.isNodeKeyDecrypted() || !megaApi.areCredentialsVerified(megaApi.getContact(String.valueOf(node.getOwner()))); - } - @SuppressLint("NonConstantResourceId") @Override public void onClick(View v) { @@ -1007,7 +1003,23 @@ public void onClick(View v) { break; case R.id.share_folder_option: - showShareFolderOptions(); + if(incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() + && + (!node.isNodeKeyDecrypted() || + !megaApi.areCredentialsVerified( + megaApi.getContact(String.valueOf(node.getOwner()))) + ) + ) { + megaApi.openShareDialog(node, new OptionalMegaRequestListenerInterface() { + @Override + public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { + super.onRequestFinish(api, request, error); + showShareFolderOptions(); + } + }); + } else { + showShareFolderOptions(); + } break; case R.id.clear_share_option: diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index b6f5288338c..023dc0beeeb 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -72,6 +72,9 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter + private lateinit var unverifiedOutgoingNodeHandles: HashSet + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecentActionViewHolder { val binding = ItemBucketBinding.inflate(LayoutInflater.from(parent.context), parent, false) @@ -191,7 +194,7 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter) { + unverifiedIncomingNodeHandles = HashSet() + unverifiedIncomingNodeHandles.addAll(handles) + } + + fun setUnverifiedOutgoingNodeHandles(handles: List) { + unverifiedOutgoingNodeHandles = HashSet() + unverifiedOutgoingNodeHandles.addAll(handles) + } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt index 164f4422097..b971efcdc97 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt @@ -35,6 +35,8 @@ import mega.privacy.android.app.main.PdfViewerActivity import mega.privacy.android.app.modalbottomsheet.NodeOptionsBottomSheetDialogFragment import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity import mega.privacy.android.app.presentation.recentactions.model.RecentActionItemType +import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel +import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel import mega.privacy.android.app.utils.Constants import mega.privacy.android.app.utils.FileUtil import mega.privacy.android.app.utils.MegaApiUtils @@ -77,6 +79,8 @@ class RecentActionsFragment : Fragment() { private lateinit var fastScroller: FastScroller private val viewModel: RecentActionsViewModel by activityViewModels() + private val incomingSharesViewModel: IncomingSharesViewModel by activityViewModels() + private val outGoingSharesViewModel: OutgoingSharesViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, @@ -122,6 +126,22 @@ class RecentActionsFragment : Fragment() { fastScroller = binding.fastscroll initAdapter() + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + incomingSharesViewModel.state.collect { + adapter.setUnverifiedIncomingNodeHandles(it.unVerifiedIncomingNodeHandles) + } + } + } + + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + outGoingSharesViewModel.state.collect { + adapter.setUnverifiedOutgoingNodeHandles(it.unVerifiedOutgoingNodeHandles) + } + } + } + } /** @@ -130,7 +150,9 @@ class RecentActionsFragment : Fragment() { private fun initAdapter() { adapter.setOnItemClickListener { item, position -> - if (!item.isKeyVerified) { + if (incomingSharesViewModel.state.value.unVerifiedIncomingNodeHandles.contains(item.bucket.nodes[0].id.longValue) + || outGoingSharesViewModel.state.value.unVerifiedOutgoingNodeHandles.contains(item.bucket.nodes[0].id.longValue) + ) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, item.bucket.userEmail) requireActivity().startActivity(this) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index f7e5a28403c..333d277e919 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -168,7 +168,7 @@ class RecentActionsViewModel @Inject constructor( val parentNode = getNodeByHandle(bucket.parentHandle) val sharesType = getParentSharesType(parentNode) val isNodeKeyDecrypted = - bucket.nodes[0].isNodeKeyDecrypted && areCredentialsVerified(bucket.userEmail) + !bucket.nodes[0].isNodeKeyDecrypted && !areCredentialsVerified(bucket.userEmail) recentItemList.add(RecentActionItemType.Item( bucket, userName, diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 9df3393bb35..5a69738d9c8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -97,10 +97,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { override fun itemClick(position: Int) { val actualPosition = position - 1 - - if (!state().nodes[actualPosition].isNodeKeyDecrypted || - !megaApi.areCredentialsVerified(megaApi.getContact(state().nodes[actualPosition].owner.toString())) - ) { + if (state().unVerifiedIncomingNodeHandles.contains(state().nodes[actualPosition].handle)) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, ContactUtil.getContactEmailDB(state().nodes[actualPosition].owner)) diff --git a/app/src/qa/assets/featuretoggle/feature_flags.json b/app/src/qa/assets/featuretoggle/feature_flags.json index 57ca03d12da..903fef89531 100644 --- a/app/src/qa/assets/featuretoggle/feature_flags.json +++ b/app/src/qa/assets/featuretoggle/feature_flags.json @@ -9,6 +9,6 @@ }, { "name": "SetSecureFlag", - "value": true + "value": false } ] \ No newline at end of file From 14e25161bd0ad51e8830a8dcde6330efdb712808 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 7 Feb 2023 23:40:45 +0530 Subject: [PATCH 195/334] Code review comments resolved --- .../privacy/android/app/di/GetNodeModule.kt | 4 +- .../app/di/featuretoggle/FeatureFlagModule.kt | 11 + .../android/app/main/ManagerActivity.java | 380 ++---------------- .../privacy/android/app/main/TourFragment.kt | 11 +- .../NodeOptionsBottomSheetDialogFragment.java | 1 - .../recentactions/RecentActionsViewModel.kt | 3 - .../presentation/search/SearchViewModel.kt | 2 +- .../incoming/IncomingSharesViewModel.kt | 4 +- .../outgoing/OutgoingSharesViewModel.kt | 4 +- .../outgoing/model/OutgoingSharesState.kt | 3 + .../android/app/di/TestGetNodeModule.kt | 4 +- .../RecentActionsViewModelTest.kt | 6 - .../incoming/IncomingSharesViewModelTest.kt | 2 +- 13 files changed, 64 insertions(+), 371 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt index 6736fcc4c15..0c723394994 100644 --- a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt @@ -8,11 +8,11 @@ import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.rx3.await import mega.privacy.android.app.domain.usecase.CheckNameCollision import mega.privacy.android.app.domain.usecase.CopyNode +import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.namecollision.usecase.CheckNameCollisionUseCase import mega.privacy.android.app.usecase.MoveNodeUseCase -import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares import mega.privacy.android.domain.usecase.SetSecureFlag @@ -92,7 +92,7 @@ abstract class GetNodeModule { */ @Provides fun provideGetUnVerifiedInComingShares(megaNodeRepository: MegaNodeRepository): GetUnverifiedIncomingShares = - GetUnverifiedIncomingShares(megaNodeRepository::getUnVerifiedInComingShares) + GetUnverifiedIncomingShares(megaNodeRepository::getUnverifiedIncomingShares) /** * Provides [GetUnverifiedOutgoingShares] implementation diff --git a/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt b/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt index f158d626f02..2e71d60bf55 100644 --- a/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt @@ -10,11 +10,13 @@ import dagger.multibindings.IntoMap import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.data.featuretoggle.file.FileFeatureFlagValueProvider import mega.privacy.android.data.qualifier.FeatureFlagPriorityKey +import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.domain.entity.Feature import mega.privacy.android.domain.featuretoggle.FeatureFlagValuePriority import mega.privacy.android.domain.featuretoggle.FeatureFlagValueProvider import mega.privacy.android.domain.usecase.DefaultGetFeatureFlagValue import mega.privacy.android.domain.usecase.GetFeatureFlagValue +import mega.privacy.android.domain.usecase.SetSecureFlag /** * Feature flag module @@ -68,5 +70,14 @@ abstract class FeatureFlagModule { fun provideFeatureFlagValueProvider(): @JvmSuppressWildcards FeatureFlagValueProvider = AppFeatures.Companion + /** + * Provides [SetSecureFlag] implementation + * + * @param filesRepository [FilesRepository] + * @return [SetSecureFlag] + */ + @Provides + fun provideSetSecureFlag(megaNodeRepository: MegaNodeRepository): SetSecureFlag = + SetSecureFlag(megaNodeRepository::setSecureFlag) } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 588ad7e78eb..00e0daf6bcc 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -355,9 +355,11 @@ import mega.privacy.android.app.objects.PasscodeManagement; import mega.privacy.android.app.presentation.clouddrive.FileBrowserFragment; import mega.privacy.android.app.presentation.clouddrive.FileBrowserViewModel; +import mega.privacy.android.app.presentation.fileinfo.FileInfoActivity; import mega.privacy.android.app.presentation.fingerprintauth.SecurityUpgradeDialogFragment; import mega.privacy.android.app.presentation.inbox.InboxFragment; import mega.privacy.android.app.presentation.inbox.InboxViewModel; +import mega.privacy.android.app.presentation.login.LoginActivity; import mega.privacy.android.app.presentation.manager.ManagerViewModel; import mega.privacy.android.app.presentation.manager.UnreadUserAlertsCheckType; import mega.privacy.android.app.presentation.manager.UserInfoViewModel; @@ -371,6 +373,7 @@ import mega.privacy.android.app.presentation.photos.albums.AlbumDynamicContentFragment; import mega.privacy.android.app.presentation.photos.mediadiscovery.MediaDiscoveryFragment; import mega.privacy.android.app.presentation.photos.timeline.photosfilter.PhotosFilterFragment; +import mega.privacy.android.app.presentation.qrcode.scan.ScanCodeFragment; import mega.privacy.android.app.presentation.rubbishbin.RubbishBinFragment; import mega.privacy.android.app.presentation.rubbishbin.RubbishBinViewModel; import mega.privacy.android.app.presentation.search.SearchFragment; @@ -435,6 +438,7 @@ import mega.privacy.android.domain.entity.contacts.ContactRequest; import mega.privacy.android.domain.entity.contacts.ContactRequestStatus; import mega.privacy.android.domain.entity.preference.ViewType; +import mega.privacy.android.domain.entity.user.UserCredentials; import mega.privacy.android.domain.qualifier.ApplicationScope; import nz.mega.documentscanner.DocumentScannerActivity; import nz.mega.sdk.MegaAccountDetails; @@ -613,14 +617,8 @@ public class ManagerActivity extends TransfersManagementActivity MegaNode parentNodeManager; public DrawerLayout drawerLayout; - ArrayList contacts = new ArrayList<>(); - ArrayList visibleContacts = new ArrayList<>(); public boolean openFolderRefresh = false; - - public boolean openSettingsStartScreen; - public boolean openSettingsStorage = false; - public boolean openSettingsQR = false; boolean newAccount = false; public boolean newCreationAccount; @@ -630,8 +628,6 @@ public class ManagerActivity extends TransfersManagementActivity private boolean isStorageStatusDialogShown = false; - private boolean isTransferOverQuotaWarningShown; - private AlertDialog transferOverQuotaWarning; private AlertDialog confirmationTransfersDialog; private AlertDialog reconnectDialog; @@ -653,56 +649,6 @@ public class ManagerActivity extends TransfersManagementActivity private boolean isInAlbumContent; public boolean fromAlbumContent = false; - public enum FragmentTag { - CLOUD_DRIVE, HOMEPAGE, PHOTOS, INBOX, INCOMING_SHARES, OUTGOING_SHARES, SEARCH, TRANSFERS, COMPLETED_TRANSFERS, - RECENT_CHAT, RUBBISH_BIN, NOTIFICATIONS, TURN_ON_NOTIFICATIONS, PERMISSIONS, SMS_VERIFICATION, - LINKS, MEDIA_DISCOVERY, ALBUM_CONTENT, PHOTOS_FILTER; - - public String getTag() { - switch (this) { - case CLOUD_DRIVE: - return "fileBrowserFragment"; - case HOMEPAGE: - return "homepageFragment"; - case RUBBISH_BIN: - return "rubbishBinFragment"; - case PHOTOS: - return "photosFragment"; - case INBOX: - return "inboxFragment"; - case INCOMING_SHARES: - return "incomingSharesFragment"; - case OUTGOING_SHARES: - return "outgoingSharesFragment"; - case SEARCH: - return "searchFragment"; - case TRANSFERS: - return "android:switcher:" + R.id.transfers_tabs_pager + ":" + 0; - case COMPLETED_TRANSFERS: - return "android:switcher:" + R.id.transfers_tabs_pager + ":" + 1; - case RECENT_CHAT: - return "chatTabsFragment"; - case NOTIFICATIONS: - return "notificationsFragment"; - case TURN_ON_NOTIFICATIONS: - return "turnOnNotificationsFragment"; - case PERMISSIONS: - return "permissionsFragment"; - case SMS_VERIFICATION: - return "smsVerificationFragment"; - case LINKS: - return "linksFragment"; - case MEDIA_DISCOVERY: - return "mediaDiscoveryFragment"; - case ALBUM_CONTENT: - return "fragmentAlbumContent"; - case PHOTOS_FILTER: - return "fragmentPhotosFilter"; - } - return null; - } - } - public boolean turnOnNotifications = false; private DrawerItem drawerItem; @@ -736,8 +682,6 @@ public String getTag() { private RelativeLayout callInProgressLayout; private Chronometer callInProgressChrono; private TextView callInProgressText; - private LinearLayout microOffLayout; - private LinearLayout videoOnLayout; boolean firstTimeAfterInstallation = true; SearchView searchView; @@ -754,11 +698,6 @@ public String getTag() { private HomepageScreen mHomepageScreen = HomepageScreen.HOMEPAGE; - private enum HomepageScreen { - HOMEPAGE, IMAGES, FAVOURITES, DOCUMENTS, AUDIO, VIDEO, - FULLSCREEN_OFFLINE, OFFLINE_FILE_INFO, RECENT_BUCKET - } - public boolean isList = true; private String pathNavigationOffline; @@ -777,7 +716,6 @@ private enum HomepageScreen { private Fragment albumContentFragment; private PhotosFilterFragment photosFilterFragment; private ChatTabsFragment chatTabsFragment; - private NotificationsFragment notificationsFragment; private TurnOnNotificationsFragment turnOnNotificationsFragment; private PermissionsFragment permissionsFragment; private SMSVerificationFragment smsVerificationFragment; @@ -793,17 +731,12 @@ private enum HomepageScreen { private AlertDialog permissionsDialog; private AlertDialog presenceStatusDialog; - private AlertDialog alertNotPermissionsUpload; - private AlertDialog clearRubbishBinDialog; - private AlertDialog insertPassDialog; - private AlertDialog changeUserAttributeDialog; private AlertDialog alertDialogStorageStatus; private AlertDialog alertDialogSMSVerification; private AlertDialog newTextFileDialog; private AlertDialog newFolderDialog; private MenuItem searchMenuItem; - private MenuItem enableSelectMenuItem; private MenuItem doNotDisturbMenuItem; private MenuItem clearRubbishBinMenuitem; private MenuItem cancelAllTransfersMenuItem; @@ -811,7 +744,6 @@ private enum HomepageScreen { private MenuItem pauseTransfersMenuIcon; private MenuItem retryTransfers; private MenuItem clearCompletedTransfers; - private MenuItem scanQRcodeMenuItem; private MenuItem returnCallMenuItem; private MenuItem openLinkMenuItem; private Chronometer chronometerMenuItem; @@ -823,8 +755,6 @@ private enum HomepageScreen { Button enable2FAButton; Button skip2FAButton; - private boolean is2FAEnabled = false; - public boolean comesFromNotifications = false; public int comesFromNotificationsLevel = 0; public long comesFromNotificationHandle = INVALID_VALUE; @@ -847,6 +777,7 @@ private enum HomepageScreen { int bottomNavigationCurrentItem = -1; View chatBadge; View callBadge; + BottomNavigationMenuView menuView; private boolean joiningToChatLink; private String linkJoinToChatLink; @@ -919,8 +850,6 @@ public void onChanged(Boolean aBoolean) { private final ArrayList fabs = new ArrayList<>(); // end for Meeting - // Backup warning dialog - private AlertDialog backupWarningDialog; private ArrayList backupHandleList; private int backupDialogType = BACKUP_DIALOG_SHOW_NONE; private Long backupNodeHandle; @@ -1209,35 +1138,6 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis break; } - case REQUEST_CAMERA_UPLOAD: - case REQUEST_CAMERA_ON_OFF: - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - checkIfShouldShowBusinessCUAlert(); - } else { - stopCameraUploadSyncHeartbeatWorkers(this); - showSnackbar(SNACKBAR_TYPE, getString(R.string.on_refuse_storage_permission), INVALID_HANDLE); - } - - break; - - case REQUEST_CAMERA_ON_OFF_FIRST_TIME: - if (permissions.length == 0) { - return; - } - if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - checkIfShouldShowBusinessCUAlert(); - } else { - if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[0])) { - if (getPhotosFragment() != null) { - photosFragment.onStoragePermissionRefused(); - } - } else { - showSnackbar(SNACKBAR_TYPE, getString(R.string.on_refuse_storage_permission), INVALID_HANDLE); - } - } - - break; - case PERMISSIONS_FRAGMENT: { if (getPermissionsFragment() != null) { permissionsFragment.setNextPermission(); @@ -1321,7 +1221,6 @@ public void onSaveInstanceState(Bundle outState) { outState.putBoolean(BUSINESS_CU_ALERT_SHOWN, isBusinessCUAlertShown); } - outState.putBoolean(TRANSFER_OVER_QUOTA_SHOWN, isTransferOverQuotaWarningShown); outState.putInt(TYPE_CALL_PERMISSION, typesCameraPermission); outState.putBoolean(JOINING_CHAT_LINK, joiningToChatLink); outState.putString(LINK_JOINING_CHAT_LINK, linkJoinToChatLink); @@ -1495,7 +1394,6 @@ protected void onCreate(Bundle savedInstanceState) { openLinkDialogIsShown = savedInstanceState.getBoolean(OPEN_LINK_DIALOG_SHOWN, false); isBusinessGraceAlertShown = savedInstanceState.getBoolean(BUSINESS_GRACE_ALERT_SHOWN, false); isBusinessCUAlertShown = savedInstanceState.getBoolean(BUSINESS_CU_ALERT_SHOWN, false); - isTransferOverQuotaWarningShown = savedInstanceState.getBoolean(TRANSFER_OVER_QUOTA_SHOWN, false); typesCameraPermission = savedInstanceState.getInt(TYPE_CALL_PERMISSION, INVALID_TYPE_PERMISSIONS); joiningToChatLink = savedInstanceState.getBoolean(JOINING_CHAT_LINK, false); linkJoinToChatLink = savedInstanceState.getString(LINK_JOINING_CHAT_LINK); @@ -1557,11 +1455,6 @@ protected void onCreate(Bundle savedInstanceState) { LiveEventBus.get(EVENT_REFRESH_PHONE_NUMBER, Boolean.class) .observeForever(refreshAddPhoneNumberButtonObserver); - LiveEventBus.get(EVENT_TRANSFER_OVER_QUOTA, Boolean.class).observe(this, update -> { - updateTransfersWidget(TransferType.NONE); - showTransfersTransferOverQuotaWarning(); - }); - LiveEventBus.get(EVENT_FAILED_TRANSFERS, Boolean.class).observe(this, failed -> { if (drawerItem == DrawerItem.TRANSFERS && getTabItemTransfers() == TransfersTab.COMPLETED_TAB) { retryTransfers.setVisible(failed); @@ -1802,7 +1695,7 @@ public boolean onPreDraw() { badgeDrawable = new BadgeDrawerArrowDrawable(managerActivity, R.color.red_600_red_300, R.color.white_dark_grey, R.color.white_dark_grey); - BottomNavigationMenuView menuView = (BottomNavigationMenuView) bNV.getChildAt(0); + menuView = (BottomNavigationMenuView) bNV.getChildAt(0); // Navi button Chat BottomNavigationItemView itemView = (BottomNavigationItemView) menuView.getChildAt(3); chatBadge = LayoutInflater.from(this).inflate(R.layout.bottom_chat_badge, menuView, false); @@ -1961,8 +1854,6 @@ public void onPageScrollStateChanged(int state) { callInProgressLayout.setOnClickListener(this); callInProgressChrono = findViewById(R.id.call_in_progress_chrono); callInProgressText = findViewById(R.id.call_in_progress_text); - microOffLayout = findViewById(R.id.micro_off_layout); - videoOnLayout = findViewById(R.id.video_on_layout); callInProgressLayout.setVisibility(View.GONE); if (mElevationCause > 0) { @@ -2262,7 +2153,7 @@ public void onPageScrollStateChanged(int state) { selectDrawerItemPending = false; } else if (fragmentHandle == megaApi.getRubbishNode().getHandle()) { drawerItem = DrawerItem.RUBBISH_BIN; - viewModel.setRubbishBinParentHandle(handleIntent); + rubbishBinViewModel.setRubbishBinHandle(handleIntent); selectDrawerItem(drawerItem); selectDrawerItemPending = false; } else if (fragmentHandle == megaApi.getInboxNode().getHandle()) { @@ -2565,10 +2456,6 @@ public void onPageScrollStateChanged(int state) { } } - if (drawerItem == DrawerItem.TRANSFERS && isTransferOverQuotaWarningShown) { - showTransfersTransferOverQuotaWarning(); - } - PsaManager.INSTANCE.startChecking(); if (savedInstanceState != null && savedInstanceState.getBoolean(IS_NEW_TEXT_FILE_SHOWN, false)) { @@ -2598,21 +2485,6 @@ public void onPageScrollStateChanged(int state) { } else { Timber.d("Backup warning dialog is not show"); } - ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { - if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { - addUnverifiedIncomingCountBadge(incomingSharesState.getUnverifiedIncomingShares().size()); - } - return Unit.INSTANCE; - }); - - ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { - if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { - addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnverifiedOutgoingShares().size()); - } - return Unit.INSTANCE; - }); - - setPendingActionsBadge(menuView); } /** @@ -2661,6 +2533,31 @@ private void collectFlows() { nVEmail.setText(state.getEmail()); return Unit.INSTANCE; }); + + ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { + if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { + addUnverifiedIncomingCountBadge(incomingSharesState.getUnverifiedIncomingShares().size()); + } + return Unit.INSTANCE; + }); + + ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { + if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { + addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnverifiedOutgoingShares().size()); + } + return Unit.INSTANCE; + }); + + ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { + if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { + BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); + View pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_pending_actions_badge, menuView, false); + sharedItemsView.addView(pendingActionsBadge); + TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.pending_actions_badge_text); + tvPendingActionsCount.setText(String.valueOf(managerState.getPendingActionsCount())); + } + return Unit.INSTANCE; + }); } /** @@ -2776,32 +2673,6 @@ private void showBusinessGraceAlert() { isBusinessGraceAlertShown = true; } - /** - * If the account is business and not a master user, it shows a warning. - * Otherwise proceeds to enable CU. - */ - public void checkIfShouldShowBusinessCUAlert() { - if (isBusinessAccount() && !megaApi.isMasterBusinessAccount()) { - showBusinessCUAlert(); - } else { - enableCUClicked(); - } - } - - - /** - * Proceeds to enable CU action. - */ - private void enableCUClicked() { - if (getPhotosFragment() != null) { - if (photosFragment.isEnablePhotosViewShown()) { - photosFragment.enableCameraUpload(); - } else { - photosFragment.enableCameraUploadClick(); - } - } - } - /** * Shows a warning to business users about the risks of enabling CU. */ @@ -3174,7 +3045,7 @@ void actionOpenFolder(long handleIntent) { default: if (megaApi.isInRubbish(parentIntentN)) { - viewModel.setRubbishBinParentHandle(handleIntent); + rubbishBinViewModel.setRubbishBinHandle(handleIntent); drawerItem = DrawerItem.RUBBISH_BIN; } else if (megaApi.isInInbox(parentIntentN)) { inboxViewModel.updateInboxHandle(handleIntent); @@ -3479,7 +3350,6 @@ protected void onPostResume() { } case NOTIFICATIONS: break; - } case HOMEPAGE: default: setBottomNavigationMenuItemChecked(HOME_BNV); @@ -3590,8 +3460,6 @@ protected void onDestroy() { LiveEventBus.get(EVENT_FINISH_ACTIVITY, Boolean.class).removeObserver(finishObserver); LiveEventBus.get(EVENT_FAB_CHANGE, Boolean.class).removeObserver(fabChangeObserver); - destroyPayments(); - cancelSearch(); if (reconnectDialog != null) { reconnectDialog.cancel(); @@ -3806,12 +3674,12 @@ public void setToolbarTitle() { } case RUBBISH_BIN: { aB.setSubtitle(null); - MegaNode node = megaApi.getNodeByHandle(viewModel.getState().getValue().getRubbishBinParentHandle()); + MegaNode node = megaApi.getNodeByHandle(rubbishBinState(ManagerActivity.this).getRubbishBinHandle()); MegaNode rubbishNode = megaApi.getRubbishNode(); if (rubbishNode == null) { - viewModel.setRubbishBinParentHandle(INVALID_HANDLE); + rubbishBinViewModel.setRubbishBinHandle(INVALID_HANDLE); viewModel.setIsFirstNavigationLevel(true); - } else if (viewModel.getState().getValue().getRubbishBinParentHandle() == INVALID_HANDLE || node == null || node.getHandle() == rubbishNode.getHandle()) { + } else if (rubbishBinState(ManagerActivity.this).getRubbishBinHandle() == INVALID_HANDLE || node == null || node.getHandle() == rubbishNode.getHandle()) { aB.setTitle(StringResourcesUtils.getString(R.string.section_rubbish_bin)); viewModel.setIsFirstNavigationLevel(true); } else { @@ -4566,7 +4434,6 @@ private void resetCUFragment() { cuViewTypes.setVisibility(View.GONE); if (getPhotosFragment() != null) { - photosFragment.setDefaultView(); showBottomView(); } } @@ -4696,7 +4563,6 @@ public void selectDrawerItem(DrawerItem item) { supportInvalidateOptionsMenu(); showFabButton(); showHideBottomNavigationView(false); - refreshCUNodes(); if (!comesFromNotifications) { bottomNavigationCurrentItem = PHOTOS_BNV; } @@ -4771,14 +4637,6 @@ public void selectDrawerItem(DrawerItem item) { } case CHAT: { Timber.d("Chat selected"); - if (megaApi != null) { - contacts = megaApi.getContacts(); - for (int i = 0; i < contacts.size(); i++) { - if (contacts.get(i).getVisibility() == MegaUser.VISIBILITY_VISIBLE) { - visibleContacts.add(contacts.get(i)); - } - } - } selectDrawerItemChat(); supportInvalidateOptionsMenu(); showHideBottomNavigationView(false); @@ -4963,12 +4821,6 @@ public void checkScrollElevation() { } break; } - case PHOTOS: { - if (getPhotosFragment() != null) { - photosFragment.checkScroll(); - } - break; - } case INBOX: { inboxFragment = (InboxFragment) getSupportFragmentManager().findFragmentByTag(FragmentTag.INBOX.getTag()); if (inboxFragment != null) { @@ -5291,7 +5143,7 @@ public boolean onQueryTextChange(String newText) { searchViewModel.setSearchQuery(newText); searchViewModel.performSearch( fileBrowserState(ManagerActivity.this).getFileBrowserHandle(), - viewModel.getState().getValue().getRubbishBinParentHandle(), + rubbishBinState(ManagerActivity.this).getRubbishBinHandle(), inboxState(ManagerActivity.this).getInboxHandle(), incomingSharesState(ManagerActivity.this).getIncomingHandle(), outgoingSharesState(ManagerActivity.this).getOutgoingHandle(), @@ -5312,7 +5164,6 @@ public boolean onQueryTextChange(String newText) { retryTransfers = menu.findItem(R.id.action_menu_retry_transfers); playTransfersMenuIcon = menu.findItem(R.id.action_play); pauseTransfersMenuIcon = menu.findItem(R.id.action_pause); - scanQRcodeMenuItem = menu.findItem(R.id.action_scan_qr); returnCallMenuItem = menu.findItem(R.id.action_return_call); RelativeLayout rootView = (RelativeLayout) returnCallMenuItem.getActionView(); layoutCallMenuItem = rootView.findViewById(R.id.layout_menu_call); @@ -6222,7 +6073,7 @@ private void proceedWithRestoration(List nodes) { .subscribe((result, throwable) -> { if (throwable == null) { boolean notValidView = result.isSingleAction() && result.isSuccess() - && viewModel.getState().getValue().getRubbishBinParentHandle() == nodes.get(0).getHandle(); + && rubbishBinState(ManagerActivity.this).getRubbishBinHandle() == nodes.get(0).getHandle(); showRestorationOrRemovalResult(notValidView, result.getResultText()); } else if (throwable instanceof ForeignNodeException) { @@ -6241,7 +6092,6 @@ private void showRestorationOrRemovalResult(boolean notValidView, String message if (notValidView) { rubbishBinViewModel.setRubbishBinHandle(INVALID_HANDLE); setToolbarTitle(); - refreshRubbishBin(); } dismissAlertDialogIfExists(statusDialog); @@ -7329,12 +7179,6 @@ public void cameraUploadsClicked() { selectDrawerItem(drawerItem); } - public void skipInitialCUSetup() { - viewModel.setIsFirstLogin(false); - drawerItem = getStartDrawerItem(); - selectDrawerItem(drawerItem); - } - /** * Refresh the UI of the Photos feature */ @@ -7351,27 +7195,6 @@ public void refreshPhotosFragment() { } } - /** - * Checks if should update some cu view visibility. - * - * @param visibility New requested visibility update. - * @return True if should apply the visibility update, false otherwise. - */ - private boolean rightCUVisibilityChange(int visibility) { - return drawerItem == DrawerItem.PHOTOS || visibility == View.GONE; - } - - /** - * Updates cuViewTypes view visibility. - * - * @param visibility New visibility value to set. - */ - public void updateCUViewTypes(int visibility) { - if (rightCUVisibilityChange(visibility)) { - cuViewTypes.setVisibility(visibility); - } - } - /** * Shows the bottom sheet to manage a completed transfer. * @@ -7624,8 +7447,6 @@ public void refreshCloudDrive() { if (comesFromNotificationChildNodeHandleList == null) { fileBrowserFragment.hideMultipleSelect(); } - fileBrowserFragment.setNodes(nodes); - fileBrowserFragment.getRecyclerView().invalidate(); } } @@ -7659,12 +7480,6 @@ public void refreshOthersOrder() { refreshSearch(); } - public void refreshCUNodes() { - if (getPhotosFragment() != null) { - photosFragment.loadPhotos(); - } - } - public void setFirstNavigationLevel(boolean firstNavigationLevel) { Timber.d("Set value to: %s", firstNavigationLevel); viewModel.setIsFirstNavigationLevel(firstNavigationLevel); @@ -9332,8 +9147,6 @@ public void onRequestFinish(MegaApiJava api, MegaRequest request, MegaError e) { Timber.d("Attribute USER_ATTR_GEOLOCATION disabled"); MegaApplication.setEnabledGeoLocation(false); } - } else if (request.getParamType() == MegaApiJava.USER_ATTR_DISABLE_VERSIONS) { - MegaApplication.setDisableFileVersions(request.getFlag()); } } else if (request.getType() == MegaRequest.TYPE_GET_CANCEL_LINK) { Timber.d("TYPE_GET_CANCEL_LINK"); @@ -9566,19 +9379,6 @@ public void onRequestFinish(MegaApiJava api, MegaRequest request, MegaError e) { } } - /** - * Updates own firstName/lastName and fullName data in UI and DB. - * - * @param firstName True if the update makes reference to the firstName, false it to the lastName. - * @param newName New firstName/lastName text. - * @param e MegaError of the request. - */ - private void updateMyData(boolean firstName, String newName, MegaError e) { - myAccountInfo.updateMyData(firstName, newName, e); - updateUserNameNavigationView(myAccountInfo.getFullName()); - LiveEventBus.get(EVENT_USER_NAME_UPDATED, Boolean.class).post(true); - } - @Override public void onRequestTemporaryError(MegaApiJava api, MegaRequest request, MegaError e) { @@ -9719,10 +9519,6 @@ public void openLocation(long nodeHandle, long[] childNodeHandleList) { public void updateUserAlerts(List userAlerts) { viewModel.checkNumUnreadUserAlerts(UnreadUserAlertsCheckType.NOTIFICATIONS_TITLE_AND_TOOLBAR_ICON); - notificationsFragment = (NotificationsFragment) getSupportFragmentManager().findFragmentByTag(FragmentTag.NOTIFICATIONS.getTag()); - if (notificationsFragment != null && userAlerts != null) { - notificationsFragment.updateNotifications(userAlerts); - } } public void updateMyEmail(String email) { @@ -10281,7 +10077,6 @@ public AndroidCompletedTransfer getSelectedTransfer() { } public MegaNode getSelectedNode() { - callOpenShareDialog(); return selectedNode; } @@ -10514,17 +10309,6 @@ public void setChatBadge() { } } - private void callOpenShareDialog() { - if (searchViewModel.getState().getValue().isMandatoryFingerPrintVerificationRequired()) { - megaApi.openShareDialog(selectedNode, new OptionalMegaRequestListenerInterface() { - @Override - public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { - super.onRequestFinish(api, request, error); - } - }); - } - } - public void setPendingActionsBadge(BottomNavigationMenuView menuView) { ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { @@ -10574,31 +10358,6 @@ public void refreshMenu() { supportInvalidateOptionsMenu(); } - public boolean is2FAEnabled() { - return is2FAEnabled; - } - - /** - * Sets or removes the layout behaviour to hide the bottom view when scrolling. - * - * @param enable True if should set the behaviour, false if should remove it. - */ - public void enableHideBottomViewOnScroll(boolean enable) { - LinearLayout layout = findViewById(R.id.container_bottom); - if (layout == null || isInImagesPage()) { - return; - } - - final CoordinatorLayout.LayoutParams fParams - = new CoordinatorLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); - fParams.setMargins(0, 0, 0, enable ? 0 : getResources().getDimensionPixelSize(R.dimen.bottom_navigation_view_height)); - fragmentLayout.setLayoutParams(fParams); - - CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) layout.getLayoutParams(); - params.setBehavior(enable ? new CustomHideBottomViewOnScrollBehaviour() : null); - layout.setLayoutParams(params); - } - /** * Shows all the content of bottom view. */ @@ -10613,33 +10372,6 @@ public void showBottomView() { .start(); } - /** - * Shows or hides the bottom view and animates the transition. - * - * @param hide True if should hide it, false if should show it. - */ - public void animateBottomView(boolean hide) { - LinearLayout bottomView = findViewById(R.id.container_bottom); - if (bottomView == null || fragmentLayout == null) { - return; - } - - CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) fragmentLayout.getLayoutParams(); - - if (hide && bottomView.getVisibility() == View.VISIBLE) { - bottomView.animate().translationY(bottomView.getHeight()).setDuration(ANIMATION_DURATION) - .withStartAction(() -> params.bottomMargin = 0) - .withEndAction(() -> bottomView.setVisibility(View.GONE)).start(); - } else if (!hide && bottomView.getVisibility() == View.GONE) { - int bottomMargin = getResources().getDimensionPixelSize(R.dimen.bottom_navigation_view_height); - - bottomView.animate().translationY(0).setDuration(ANIMATION_DURATION) - .withStartAction(() -> bottomView.setVisibility(View.VISIBLE)) - .withEndAction(() -> params.bottomMargin = bottomMargin) - .start(); - } - } - public void showHideBottomNavigationView(boolean hide) { if (bNV == null) return; @@ -10955,29 +10687,6 @@ public void viewNodeInFolder(MegaNode node) { } } - /** - * Shows a "transfer over quota" warning. - */ - public void showTransfersTransferOverQuotaWarning() { - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); - int messageResource = R.string.warning_transfer_over_quota; - - transferOverQuotaWarning = builder.setTitle(R.string.label_transfer_over_quota) - .setMessage(getString(messageResource, getHumanizedTime(megaApi.getBandwidthOverquotaDelay()))) - .setPositiveButton(R.string.my_account_upgrade_pro, (dialog, which) -> { - navigateToUpgradeAccount(); - }) - .setNegativeButton(R.string.general_dismiss, null) - .setCancelable(false) - .setOnDismissListener(dialog -> isTransferOverQuotaWarningShown = false) - .create(); - - transferOverQuotaWarning.setCanceledOnTouchOutside(false); - TimeUtils.createAndShowCountDownTimer(messageResource, transferOverQuotaWarning); - transferOverQuotaWarning.show(); - isTransferOverQuotaWarningShown = true; - } - /** * Updates the position of the transfers widget. * @@ -11090,10 +10799,6 @@ private PermissionsFragment getPermissionsFragment() { return permissionsFragment = (PermissionsFragment) getSupportFragmentManager().findFragmentByTag(FragmentTag.PERMISSIONS.getTag()); } - public Fragment getMDFragment() { - return mediaDiscoveryFragment; - } - public Fragment getAlbumContentFragment() { return albumContentFragment; } @@ -11245,15 +10950,6 @@ public boolean isInPhotosPage() { return drawerItem == DrawerItem.PHOTOS; } - /** - * Checks if the current screen is Media discovery page. - * - * @return True if the current screen is Media discovery page, false otherwise. - */ - public boolean isInMDPage() { - return drawerItem == DrawerItem.CLOUD_DRIVE && isInMDMode; - } - /** * Create the instance of FileBackupManager */ diff --git a/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt b/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt index 47b6ed4273b..0892c278113 100644 --- a/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/main/TourFragment.kt @@ -26,7 +26,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import mega.privacy.android.app.BaseActivity -import mega.privacy.android.app.MegaApplication import mega.privacy.android.app.MegaApplication.Companion.isLoggingOut import mega.privacy.android.app.R import mega.privacy.android.app.TourImageAdapter @@ -38,9 +37,8 @@ import mega.privacy.android.app.meeting.fragments.PasteMeetingLinkGuestDialogFra import mega.privacy.android.app.presentation.login.LoginActivity import mega.privacy.android.app.utils.Constants import mega.privacy.android.app.utils.permission.PermissionUtils.hasPermissions -import mega.privacy.android.data.qualifier.MegaApi import mega.privacy.android.domain.usecase.GetFeatureFlagValue -import nz.mega.sdk.MegaApiAndroid +import mega.privacy.android.domain.usecase.SetSecureFlag import nz.mega.sdk.MegaApiJava import timber.log.Timber import javax.inject.Inject @@ -58,11 +56,10 @@ class TourFragment : Fragment() { private lateinit var joinMeetingAsGuestLauncher: ActivityResultLauncher @Inject - @MegaApi - lateinit var megaApi: MegaApiAndroid + lateinit var getFeatureFlagValue: GetFeatureFlagValue @Inject - lateinit var getFeatureFlagValue: GetFeatureFlagValue + lateinit var setSecureFlag: SetSecureFlag private val selectedCircle by lazy { ContextCompat.getDrawable(requireContext(), R.drawable.selection_circle_page_adapter) @@ -156,7 +153,7 @@ class TourFragment : Fragment() { } viewLifecycleOwner.lifecycleScope.launch { - megaApi.setSecureFlag(getFeatureFlagValue(AppFeatures.SetSecureFlag)) + setSecureFlag(getFeatureFlagValue(AppFeatures.SetSecureFlag)) } } diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index e34f2a07616..3273994fd1e 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -181,7 +181,6 @@ public class NodeOptionsBottomSheetDialogFragment extends BaseBottomSheetDialogF private IncomingSharesViewModel incomingSharesViewModel; private OutgoingSharesViewModel outgoingSharesViewModel; - private Set unverifiedHandles = new HashSet<>(); public NodeOptionsBottomSheetDialogFragment(int mode) { if (mode >= DEFAULT_MODE && mode <= FAVOURITES_MODE) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index 333d277e919..65f371f02d3 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -18,7 +18,6 @@ import mega.privacy.android.app.presentation.recentactions.model.RecentActionsSh import mega.privacy.android.app.presentation.recentactions.model.RecentActionsState import mega.privacy.android.domain.entity.RecentActionBucket import mega.privacy.android.domain.entity.contacts.ContactItem -import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -36,7 +35,6 @@ import javax.inject.Inject * @param getVisibleContacts * @param setHideRecentActivity * @param monitorNodeUpdates - * @param areCredentialsVerified */ @HiltViewModel class RecentActionsViewModel @Inject constructor( @@ -49,7 +47,6 @@ class RecentActionsViewModel @Inject constructor( private val getParentMegaNode: GetParentMegaNode, monitorHideRecentActivity: MonitorHideRecentActivity, monitorNodeUpdates: MonitorNodeUpdates, - val areCredentialsVerified: AreCredentialsVerified, ) : ViewModel() { private var _buckets = listOf() diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt index b8c7b5cea02..b991dcf40cb 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt @@ -334,7 +334,7 @@ class SearchViewModel @Inject constructor( /** * Gets the feature flag value & updates state */ - fun isMandatoryFingerprintRequired() { + private fun isMandatoryFingerprintRequired() { viewModelScope.launch { _state.update { it.copy(isMandatoryFingerPrintVerificationRequired = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index a458941ff23..04c3aaafaf8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -84,9 +84,7 @@ class IncomingSharesViewModel @Inject constructor( } val handles = unverifiedIncomingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .mapNotNull { shareData -> - getNodeByHandle(shareData.nodeHandle)?.handle - } + .map { shareData -> shareData.nodeHandle } _state.update { it.copy(nodes = unverifiedIncomingNodes, unverifiedIncomingShares = unverifiedIncomingShares, diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 476ae514328..d6703cd5faf 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -64,9 +64,7 @@ class OutgoingSharesViewModel @Inject constructor( val unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder) val handles = unverifiedOutgoingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .mapNotNull { shareData -> - getNodeByHandle(shareData.nodeHandle)?.handle - } + .map { shareData -> shareData.nodeHandle } _state.update { it.copy(unverifiedOutgoingShares = unverifiedOutgoingShares, unVerifiedOutgoingNodeHandles = handles) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index b326a7e9987..fde4487f23b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -2,6 +2,7 @@ package mega.privacy.android.app.presentation.shares.outgoing.model import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder +import mega.privacy.android.domain.usecase.AreCredentialsVerified import nz.mega.sdk.MegaNode /** @@ -17,6 +18,7 @@ import nz.mega.sdk.MegaNode * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles + * @param areUserCredentialsVerified Boolean value to read if user credentials are verified */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -29,6 +31,7 @@ data class OutgoingSharesState( val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodeHandles: List = emptyList(), + val areUserCredentialsVerified: Boolean = false, ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt index 26fe7ed5176..29fc17817a7 100644 --- a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt +++ b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt @@ -39,12 +39,12 @@ object TestGetNodeModule { } @Provides - fun provideGetUnVerifiedInComingShares() = mock() { + fun provideGetUnverifiedIncomingShares() = mock() { onBlocking { invoke(any()) }.thenReturn(emptyList()) } @Provides - fun provideGetUnverifiedOutGoingShares() = mock() { + fun provideGetUnverifiedOutgoingShares() = mock() { onBlocking { invoke(any()) }.thenReturn(emptyList()) } } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt index 486e1afe3c2..e07fe51f2ff 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt @@ -24,7 +24,6 @@ import mega.privacy.android.domain.entity.contacts.ContactItem import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.entity.node.TypedFileNode -import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -70,10 +69,6 @@ class RecentActionsViewModelTest { } private val monitorNodeUpdates = FakeMonitorUpdates() - private val areCredentialsVerified = mock { - onBlocking { invoke(any()) }.thenReturn(false) - } - private val node: TypedFileNode = mock { on { id }.thenReturn(NodeId(123)) on { isNodeKeyDecrypted }.thenReturn(false) @@ -120,7 +115,6 @@ class RecentActionsViewModelTest { getParentMegaNode, monitorHideRecentActivity, monitorNodeUpdates, - areCredentialsVerified, ) } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 6baf6512a1e..d423fda0313 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -528,7 +528,7 @@ class IncomingSharesViewModelTest { } @Test - fun `test that unverified outgoing shares are returned`() = runTest { + fun `test that unverified incoming shares are returned`() = runTest { val node1 = mock() whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() From 582d834737a83f01b07f7cb045f549cedb4026e8 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 10 Feb 2023 17:49:07 +0530 Subject: [PATCH 196/334] Code comments resolved --- .../privacy/android/app/di/GetNodeModule.kt | 13 ++++++++- .../app/domain/usecase/OpenShareDialog.kt | 16 ++++++++++ .../android/app/main/ManagerActivity.java | 13 --------- .../NodeOptionsBottomSheetDialogFragment.java | 29 ++++--------------- .../recentactions/RecentActionsAdapter.kt | 15 +--------- .../recentactions/RecentActionsFragment.kt | 24 +-------------- .../recentactions/RecentActionsViewModel.kt | 10 ++++--- .../incoming/IncomingSharesViewModel.kt | 7 +---- .../outgoing/OutgoingSharesViewModel.kt | 15 ++++++++++ .../outgoing/model/OutgoingSharesState.kt | 3 -- .../assets/featuretoggle/feature_flags.json | 2 +- .../android/app/di/TestGetNodeModule.kt | 4 +++ .../RecentActionsViewModelTest.kt | 15 +++++++++- .../outgoing/OutgoingSharesViewModelTest.kt | 4 +++ .../data/repository/DefaultFilesRepository.kt | 0 15 files changed, 80 insertions(+), 90 deletions(-) create mode 100644 app/src/main/java/mega/privacy/android/app/domain/usecase/OpenShareDialog.kt delete mode 100644 data/src/main/java/mega/privacy/android/data/repository/DefaultFilesRepository.kt diff --git a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt index 0c723394994..a1d33f25bbc 100644 --- a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt @@ -8,9 +8,10 @@ import dagger.hilt.components.SingletonComponent import kotlinx.coroutines.rx3.await import mega.privacy.android.app.domain.usecase.CheckNameCollision import mega.privacy.android.app.domain.usecase.CopyNode -import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle +import mega.privacy.android.app.domain.usecase.OpenShareDialog +import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.app.namecollision.usecase.CheckNameCollisionUseCase import mega.privacy.android.app.usecase.MoveNodeUseCase import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares @@ -139,5 +140,15 @@ abstract class GetNodeModule { @Provides fun provideSetSecureFlag(megaNodeRepository: MegaNodeRepository): SetSecureFlag = SetSecureFlag(megaNodeRepository::setSecureFlag) + + /** + * Provides [OpenShareDialog] implementation + * + * @param megaNodeRepository [MegaNodeRepository] + * @return [OpenShareDialog] + */ + @Provides + fun provideOpenShareDialog(megaNodeRepository: MegaNodeRepository): OpenShareDialog = + OpenShareDialog(megaNodeRepository::openShareDialog) } } diff --git a/app/src/main/java/mega/privacy/android/app/domain/usecase/OpenShareDialog.kt b/app/src/main/java/mega/privacy/android/app/domain/usecase/OpenShareDialog.kt new file mode 100644 index 00000000000..093b12f7c8d --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/domain/usecase/OpenShareDialog.kt @@ -0,0 +1,16 @@ +package mega.privacy.android.app.domain.usecase + +import nz.mega.sdk.MegaNode + +/** + * OpenShareDialog use case. This gets called when user shares a node using bottom sheet dialog + */ +fun interface OpenShareDialog { + + /** + * Invoke + * + * @param node : [MegaNode] + */ + suspend operator fun invoke(node: MegaNode) +} \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 00e0daf6bcc..9a2dfc46f3e 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -10309,19 +10309,6 @@ public void setChatBadge() { } } - public void setPendingActionsBadge(BottomNavigationMenuView menuView) { - ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { - if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { - BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); - View pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_pending_actions_badge, menuView, false); - sharedItemsView.addView(pendingActionsBadge); - TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.pending_actions_badge_text); - tvPendingActionsCount.setText(String.valueOf(managerState.getPendingActionsCount())); - } - return Unit.INSTANCE; - }); - } - private void setCallBadge() { if (!viewModel.isConnected() || megaChatApi.getNumCalls() <= 0 || (megaChatApi.getNumCalls() == 1 && participatingInACall())) { callBadge.setVisibility(View.GONE); diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 3273994fd1e..51d7548d5c9 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -76,9 +76,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; import kotlin.Unit; import mega.privacy.android.app.MegaOffline; @@ -88,14 +86,13 @@ import mega.privacy.android.app.arch.extensions.ViewExtensionsKt; import mega.privacy.android.app.imageviewer.ImageViewerActivity; import mega.privacy.android.app.interfaces.SnackbarShower; -import mega.privacy.android.app.listeners.OptionalMegaRequestListenerInterface; import mega.privacy.android.app.main.DrawerItem; import mega.privacy.android.app.main.FileContactListActivity; -import mega.privacy.android.app.presentation.fileinfo.FileInfoActivity; import mega.privacy.android.app.main.ManagerActivity; import mega.privacy.android.app.main.VersionsFileActivity; import mega.privacy.android.app.main.controllers.NodeController; import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity; +import mega.privacy.android.app.presentation.fileinfo.FileInfoActivity; import mega.privacy.android.app.presentation.manager.model.SharesTab; import mega.privacy.android.app.presentation.search.SearchViewModel; import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel; @@ -107,10 +104,7 @@ import mega.privacy.android.app.utils.ViewUtils; import mega.privacy.android.domain.entity.ShareData; import mega.privacy.android.domain.entity.SortOrder; -import nz.mega.sdk.MegaApiJava; -import nz.mega.sdk.MegaError; import nz.mega.sdk.MegaNode; -import nz.mega.sdk.MegaRequest; import nz.mega.sdk.MegaShare; import nz.mega.sdk.MegaUser; import timber.log.Timber; @@ -884,6 +878,7 @@ private void showOwnerSharedFolder() { } } } + private void setUnverifiedNodeUserName(List shareDataList) { for (int j = 0; j < shareDataList.size(); j++) { ShareData mS = shareDataList.get(j); @@ -894,6 +889,7 @@ private void setUnverifiedNodeUserName(List shareDataList) { } else { nodeInfo.setText(mS.getUser()); } + break; } } } @@ -1002,23 +998,8 @@ public void onClick(View v) { break; case R.id.share_folder_option: - if(incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() - && - (!node.isNodeKeyDecrypted() || - !megaApi.areCredentialsVerified( - megaApi.getContact(String.valueOf(node.getOwner()))) - ) - ) { - megaApi.openShareDialog(node, new OptionalMegaRequestListenerInterface() { - @Override - public void onRequestFinish(@NonNull MegaApiJava api, @NonNull MegaRequest request, @NonNull MegaError error) { - super.onRequestFinish(api, request, error); - showShareFolderOptions(); - } - }); - } else { - showShareFolderOptions(); - } + outgoingSharesViewModel.callOpenShareDialog(node.getHandle()); + showShareFolderOptions(); break; case R.id.clear_share_option: diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt index 023dc0beeeb..b6f5288338c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsAdapter.kt @@ -72,9 +72,6 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter - private lateinit var unverifiedOutgoingNodeHandles: HashSet - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecentActionViewHolder { val binding = ItemBucketBinding.inflate(LayoutInflater.from(parent.context), parent, false) @@ -194,7 +191,7 @@ class RecentActionsAdapter @Inject constructor() : RecyclerView.Adapter) { - unverifiedIncomingNodeHandles = HashSet() - unverifiedIncomingNodeHandles.addAll(handles) - } - - fun setUnverifiedOutgoingNodeHandles(handles: List) { - unverifiedOutgoingNodeHandles = HashSet() - unverifiedOutgoingNodeHandles.addAll(handles) - } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt index b971efcdc97..164f4422097 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt @@ -35,8 +35,6 @@ import mega.privacy.android.app.main.PdfViewerActivity import mega.privacy.android.app.modalbottomsheet.NodeOptionsBottomSheetDialogFragment import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity import mega.privacy.android.app.presentation.recentactions.model.RecentActionItemType -import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel -import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel import mega.privacy.android.app.utils.Constants import mega.privacy.android.app.utils.FileUtil import mega.privacy.android.app.utils.MegaApiUtils @@ -79,8 +77,6 @@ class RecentActionsFragment : Fragment() { private lateinit var fastScroller: FastScroller private val viewModel: RecentActionsViewModel by activityViewModels() - private val incomingSharesViewModel: IncomingSharesViewModel by activityViewModels() - private val outGoingSharesViewModel: OutgoingSharesViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, @@ -126,22 +122,6 @@ class RecentActionsFragment : Fragment() { fastScroller = binding.fastscroll initAdapter() - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - incomingSharesViewModel.state.collect { - adapter.setUnverifiedIncomingNodeHandles(it.unVerifiedIncomingNodeHandles) - } - } - } - - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - outGoingSharesViewModel.state.collect { - adapter.setUnverifiedOutgoingNodeHandles(it.unVerifiedOutgoingNodeHandles) - } - } - } - } /** @@ -150,9 +130,7 @@ class RecentActionsFragment : Fragment() { private fun initAdapter() { adapter.setOnItemClickListener { item, position -> - if (incomingSharesViewModel.state.value.unVerifiedIncomingNodeHandles.contains(item.bucket.nodes[0].id.longValue) - || outGoingSharesViewModel.state.value.unVerifiedOutgoingNodeHandles.contains(item.bucket.nodes[0].id.longValue) - ) { + if (!item.isKeyVerified) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { putExtra(Constants.EMAIL, item.bucket.userEmail) requireActivity().startActivity(this) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt index 65f371f02d3..8476c173583 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModel.kt @@ -18,6 +18,7 @@ import mega.privacy.android.app.presentation.recentactions.model.RecentActionsSh import mega.privacy.android.app.presentation.recentactions.model.RecentActionsState import mega.privacy.android.domain.entity.RecentActionBucket import mega.privacy.android.domain.entity.contacts.ContactItem +import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -35,6 +36,7 @@ import javax.inject.Inject * @param getVisibleContacts * @param setHideRecentActivity * @param monitorNodeUpdates + * @param areCredentialsVerified */ @HiltViewModel class RecentActionsViewModel @Inject constructor( @@ -47,6 +49,7 @@ class RecentActionsViewModel @Inject constructor( private val getParentMegaNode: GetParentMegaNode, monitorHideRecentActivity: MonitorHideRecentActivity, monitorNodeUpdates: MonitorNodeUpdates, + private val areCredentialsVerified: AreCredentialsVerified, ) : ViewModel() { private var _buckets = listOf() @@ -161,18 +164,17 @@ class RecentActionsViewModel @Inject constructor( visibleContacts.find { bucket.userEmail == it.email }?.contactData?.fullName.orEmpty() val currentUserIsOwner = getAccountDetails(false).email == bucket.userEmail - + val isNodeKeyVerified = + bucket.nodes[0].isNodeKeyDecrypted || areCredentialsVerified(bucket.userEmail) val parentNode = getNodeByHandle(bucket.parentHandle) val sharesType = getParentSharesType(parentNode) - val isNodeKeyDecrypted = - !bucket.nodes[0].isNodeKeyDecrypted && !areCredentialsVerified(bucket.userEmail) recentItemList.add(RecentActionItemType.Item( bucket, userName, parentNode?.name ?: "", sharesType, currentUserIsOwner, - isNodeKeyDecrypted, + isNodeKeyVerified, )) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 04c3aaafaf8..c1f8a0d2cf1 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -77,14 +77,10 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { val unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder) - val unverifiedIncomingNodes = unverifiedIncomingShares - .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .mapNotNull { shareData -> - getNodeByHandle(shareData.nodeHandle) - } val handles = unverifiedIncomingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } .map { shareData -> shareData.nodeHandle } + val unverifiedIncomingNodes = handles.mapNotNull { getNodeByHandle(it) } _state.update { it.copy(nodes = unverifiedIncomingNodes, unverifiedIncomingShares = unverifiedIncomingShares, @@ -93,7 +89,6 @@ class IncomingSharesViewModel @Inject constructor( } } - /** * Refresh incoming shares node */ diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index d6703cd5faf..cf597d8c0bc 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates +import mega.privacy.android.app.domain.usecase.OpenShareDialog import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.model.OutgoingSharesState import mega.privacy.android.domain.usecase.GetCloudSortOrder @@ -36,6 +37,7 @@ class OutgoingSharesViewModel @Inject constructor( monitorNodeUpdates: MonitorNodeUpdates, private val getFeatureFlagValue: GetFeatureFlagValue, private val getUnverifiedOutgoingShares: GetUnverifiedOutgoingShares, + private val openShareDialog: OpenShareDialog, ) : ViewModel() { /** private UI state */ @@ -201,4 +203,17 @@ class OutgoingSharesViewModel @Inject constructor( it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) } } + + /** + * Calls OpenShareDialog use case to create crypto key for sharing + * + * @param nodeHandle: [MegaNode] handle + */ + fun callOpenShareDialog(nodeHandle: Long) { + viewModelScope.launch { + getNodeByHandle(nodeHandle)?.let { megaNode -> + openShareDialog(megaNode) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index fde4487f23b..b326a7e9987 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -2,7 +2,6 @@ package mega.privacy.android.app.presentation.shares.outgoing.model import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder -import mega.privacy.android.domain.usecase.AreCredentialsVerified import nz.mega.sdk.MegaNode /** @@ -18,7 +17,6 @@ import nz.mega.sdk.MegaNode * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles - * @param areUserCredentialsVerified Boolean value to read if user credentials are verified */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -31,7 +29,6 @@ data class OutgoingSharesState( val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodeHandles: List = emptyList(), - val areUserCredentialsVerified: Boolean = false, ) { /** diff --git a/app/src/qa/assets/featuretoggle/feature_flags.json b/app/src/qa/assets/featuretoggle/feature_flags.json index 903fef89531..57ca03d12da 100644 --- a/app/src/qa/assets/featuretoggle/feature_flags.json +++ b/app/src/qa/assets/featuretoggle/feature_flags.json @@ -9,6 +9,6 @@ }, { "name": "SetSecureFlag", - "value": false + "value": true } ] \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt index 29fc17817a7..f6af5e4c828 100644 --- a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt +++ b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt @@ -9,6 +9,7 @@ import mega.privacy.android.app.di.GetNodeModule import mega.privacy.android.app.domain.usecase.CopyNode import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle +import mega.privacy.android.app.domain.usecase.OpenShareDialog import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares @@ -47,4 +48,7 @@ object TestGetNodeModule { fun provideGetUnverifiedOutgoingShares() = mock() { onBlocking { invoke(any()) }.thenReturn(emptyList()) } + + @Provides + fun provideOpenShareDialog() = mock() } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt index e07fe51f2ff..17c68748bd1 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/recentactions/RecentActionsViewModelTest.kt @@ -24,6 +24,7 @@ import mega.privacy.android.domain.entity.contacts.ContactItem import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.entity.node.TypedFileNode +import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetRecentActions import mega.privacy.android.domain.usecase.GetVisibleContacts @@ -101,6 +102,9 @@ class RecentActionsViewModelTest { on { this.isUpdate }.thenReturn(false) } + private val areCredentialsVerified = mock { + onBlocking { invoke(any()) }.thenReturn(true) + } @Before fun setUp() { @@ -115,6 +119,7 @@ class RecentActionsViewModelTest { getParentMegaNode, monitorHideRecentActivity, monitorNodeUpdates, + areCredentialsVerified, ) } @@ -439,5 +444,13 @@ class RecentActionsViewModelTest { assertThat(underTest.snapshotActionList).isEqualTo(expectedSnapshotActionList) } - + @Test + fun `test that isKeyVerified gets updated in recent action items`() = runTest { + whenever(getRecentActions()).thenReturn(listOf(megaRecentActionBucket)) + underTest.state.map { it.recentActionItems }.distinctUntilChanged().test { + awaitItem() + assertThat((awaitItem().filterIsInstance()[0]).isKeyVerified) + .isEqualTo(true) + } + } } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index a97d38b03e3..a23deb884d4 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode +import mega.privacy.android.app.domain.usecase.OpenShareDialog import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel import mega.privacy.android.domain.entity.ShareData @@ -65,6 +66,8 @@ class OutgoingSharesViewModelTest { onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } + private val openShareDialog = mock() + @Before fun setUp() { Dispatchers.setMain(UnconfinedTestDispatcher()) @@ -81,6 +84,7 @@ class OutgoingSharesViewModelTest { monitorNodeUpdates, getFeatureFlagValue, getUnverifiedOutgoingShares, + openShareDialog, ) } diff --git a/data/src/main/java/mega/privacy/android/data/repository/DefaultFilesRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/DefaultFilesRepository.kt deleted file mode 100644 index e69de29bb2d..00000000000 From 67abb06c0a9c751e4a02308554ae289a3f8dadc6 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 10 Feb 2023 17:56:06 +0530 Subject: [PATCH 197/334] Code comments resolved --- .../android/app/main/ManagerActivity.java | 20 +++++++++---------- .../app/main/adapters/MegaNodeAdapter.java | 11 +++++----- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 9a2dfc46f3e..a483ab5bb81 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -2509,6 +2509,15 @@ private void collectFlows() { } viewModel.nodeUpdateHandled(); } + + // Update pending actions badge on bottom navigation menu + if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { + BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); + View pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_pending_actions_badge, menuView, false); + sharedItemsView.addView(pendingActionsBadge); + TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.pending_actions_badge_text); + tvPendingActionsCount.setText(String.valueOf(managerState.getPendingActionsCount())); + } return Unit.INSTANCE; }); ViewExtensionsKt.collectFlow(this, viewModel.getOnViewTypeChanged(), Lifecycle.State.STARTED, viewType -> { @@ -2547,17 +2556,6 @@ private void collectFlows() { } return Unit.INSTANCE; }); - - ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { - if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { - BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); - View pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_pending_actions_badge, menuView, false); - sharedItemsView.addView(pendingActionsBadge); - TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.pending_actions_badge_text); - tvPendingActionsCount.setText(String.valueOf(managerState.getPendingActionsCount())); - } - return Unit.INSTANCE; - }); } /** diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index c8626a330e0..1101a22b026 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -1092,10 +1092,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - - if (isMandatoryFingerprintVerificationNeeded + boolean hasUnverifiedNodes = isMandatoryFingerprintVerificationNeeded && !unverifiedIncomingNodeHandles.isEmpty() - && unverifiedIncomingNodeHandles.contains(node.getHandle())) { + && unverifiedIncomingNodeHandles.contains(node.getHandle()); + if (hasUnverifiedNodes) { showUnverifiedNodeUi(holder, true); } holder.permissionsIcon.setVisibility(View.VISIBLE); @@ -1106,9 +1106,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else if (type == OUTGOING_SHARES_ADAPTER) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - if (isMandatoryFingerprintVerificationNeeded + boolean hasUnverifiedNodes = isMandatoryFingerprintVerificationNeeded && !unverifiedOutgoingNodeHandles.isEmpty() - && unverifiedOutgoingNodeHandles.contains(node.getHandle())) { + && unverifiedOutgoingNodeHandles.contains(node.getHandle()); + if (hasUnverifiedNodes) { showUnverifiedNodeUi(holder, false); } } From 4a82e0f7ef7024f3451c616c9302e9f53ee9b9e3 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 10 Feb 2023 18:10:47 +0530 Subject: [PATCH 198/334] code comments resolved --- .../presentation/shares/incoming/IncomingSharesViewModel.kt | 5 ++--- .../presentation/shares/outgoing/OutgoingSharesViewModel.kt | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index c1f8a0d2cf1..2d4a7343adb 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -77,10 +77,9 @@ class IncomingSharesViewModel @Inject constructor( viewModelScope.launch { val unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder) - val handles = unverifiedIncomingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .map { shareData -> shareData.nodeHandle } - val unverifiedIncomingNodes = handles.mapNotNull { getNodeByHandle(it) } + val handles = unverifiedIncomingShares.map { shareData -> shareData.nodeHandle } + val unverifiedIncomingNodes = handles.mapNotNull { handle -> getNodeByHandle(handle) } _state.update { it.copy(nodes = unverifiedIncomingNodes, unverifiedIncomingShares = unverifiedIncomingShares, diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index cf597d8c0bc..8a20536d57d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -64,8 +64,8 @@ class OutgoingSharesViewModel @Inject constructor( viewModelScope.launch { val unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder) - val handles = unverifiedOutgoingShares .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + val handles = unverifiedOutgoingShares .map { shareData -> shareData.nodeHandle } _state.update { it.copy(unverifiedOutgoingShares = unverifiedOutgoingShares, From 941bc1fa24fdcbcd16417370bfebae768abdd0b2 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 10 Feb 2023 18:46:22 +0530 Subject: [PATCH 199/334] TabItem added to tab layout file because tabLayoutShares.getTabAt returned null --- app/src/main/res/layout/activity_manager.xml | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/activity_manager.xml b/app/src/main/res/layout/activity_manager.xml index 744e1843f11..0cc82de0875 100644 --- a/app/src/main/res/layout/activity_manager.xml +++ b/app/src/main/res/layout/activity_manager.xml @@ -36,7 +36,20 @@ android:id="@+id/sliding_tabs_shares" style="@style/Widget.Mega.TabLayout" android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content"> + + + + + android:layout_weight="1" > + Date: Mon, 13 Feb 2023 16:18:58 +1300 Subject: [PATCH 200/334] Close app on click of cancel of Security upgrade dialog --- .../privacy/android/app/di/GetNodeModule.kt | 10 +++ .../SecurityUpgradeDialogFragment.kt | 64 +++++++++++++++++++ .../SecurityUpgradeViewModel.kt | 26 ++++++++ .../android/app/di/TestGetNodeModule.kt | 4 ++ .../SecurityUpgradeViewModelTest.kt | 39 +++++++++++ .../android/domain/usecase/UpgradeSecurity.kt | 12 ++++ 6 files changed, 155 insertions(+) create mode 100644 app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt create mode 100644 app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModel.kt create mode 100644 app/src/test/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModelTest.kt create mode 100644 domain/src/main/kotlin/mega/privacy/android/domain/usecase/UpgradeSecurity.kt diff --git a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt index a1d33f25bbc..5eef302b045 100644 --- a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt @@ -20,6 +20,7 @@ import mega.privacy.android.domain.usecase.SetSecureFlag import mega.privacy.android.domain.usecase.filenode.CopyNodeByHandle import mega.privacy.android.domain.usecase.filenode.CopyNodeByHandleChangingName import mega.privacy.android.domain.usecase.filenode.MoveNodeByHandle +import mega.privacy.android.domain.usecase.UpgradeSecurity /** * Get node module @@ -150,5 +151,14 @@ abstract class GetNodeModule { @Provides fun provideOpenShareDialog(megaNodeRepository: MegaNodeRepository): OpenShareDialog = OpenShareDialog(megaNodeRepository::openShareDialog) + + /** + * Provides [UpgradeSecurity] implementation + * @param megaNodeRepository [MegaNodeRepository] + * @return [UpgradeSecurity] + */ + @Provides + fun provideUpgradeSecurity(megaNodeRepository: MegaNodeRepository): UpgradeSecurity = + UpgradeSecurity(megaNodeRepository::upgradeSecurity) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt new file mode 100644 index 00000000000..b2edf33400a --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt @@ -0,0 +1,64 @@ +package mega.privacy.android.app.presentation.fingerprintauth + +import android.app.Dialog +import android.os.Bundle +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.viewModels +import collectAsStateWithLifecycle +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import mega.privacy.android.app.presentation.extensions.isDarkMode +import mega.privacy.android.core.ui.theme.AndroidTheme +import mega.privacy.android.domain.entity.ThemeMode +import mega.privacy.android.domain.usecase.GetThemeMode +import javax.inject.Inject + +/** + * SecurityUpgradeDialogFragment + */ +@AndroidEntryPoint +class SecurityUpgradeDialogFragment : DialogFragment() { + + private val securityUpgradeViewModel by viewModels() + + /** + * Current theme + */ + @Inject + lateinit var getThemeMode: GetThemeMode + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = + MaterialAlertDialogBuilder(requireContext()).setView( + ComposeView(requireContext()).apply { + setContent { + val nodeName = arguments?.getString("nodeName", "Default") as String + val mode by getThemeMode() + .collectAsStateWithLifecycle(initialValue = ThemeMode.System) + AndroidTheme(isDark = mode.isDarkMode()) { + SecurityUpgradeDialogView(folderName = nodeName, onCancelClick = { + requireActivity().finishAffinity() + }, onOkClick = { + securityUpgradeViewModel.upgradeAccountSecurity() + dismiss() + }) + } + } + } + ).create() + + companion object { + /** + * Tag for logging + */ + const val TAG = "SecurityUpgradeDialogFragment" + + /** + * Creates instance of this class + * + * @return SecurityUpgradeDialogFragment new instance + */ + fun newInstance() = SecurityUpgradeDialogFragment() + } +} \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModel.kt new file mode 100644 index 00000000000..37511a045c0 --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModel.kt @@ -0,0 +1,26 @@ +package mega.privacy.android.app.presentation.fingerprintauth + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.launch +import mega.privacy.android.domain.usecase.UpgradeSecurity +import javax.inject.Inject + +/** + * ViewModel associated with [SecurityUpgradeDialogFragment] responsible to call account security related functions + */ +@HiltViewModel +class SecurityUpgradeViewModel @Inject constructor( + private val upgradeSecurity: UpgradeSecurity, +) : ViewModel() { + + /** + * Function to upgrade account security + */ + fun upgradeAccountSecurity() { + viewModelScope.launch { + upgradeSecurity() + } + } +} \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt index f6af5e4c828..0aeed73af39 100644 --- a/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt +++ b/app/src/test/java/test/mega/privacy/android/app/di/TestGetNodeModule.kt @@ -13,6 +13,7 @@ import mega.privacy.android.app.domain.usecase.OpenShareDialog import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares +import mega.privacy.android.domain.usecase.UpgradeSecurity import nz.mega.sdk.MegaNode import org.mockito.kotlin.any import org.mockito.kotlin.mock @@ -51,4 +52,7 @@ object TestGetNodeModule { @Provides fun provideOpenShareDialog() = mock() + + @Provides + fun provideUpgradeSecurity() = mock() } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModelTest.kt new file mode 100644 index 00000000000..d1042310d6f --- /dev/null +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModelTest.kt @@ -0,0 +1,39 @@ +package test.mega.privacy.android.app.presentation.fingerprintauth + +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import mega.privacy.android.app.presentation.fingerprintauth.SecurityUpgradeViewModel +import mega.privacy.android.domain.usecase.UpgradeSecurity +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify + +@ExperimentalCoroutinesApi +class SecurityUpgradeViewModelTest { + + private lateinit var underTest: SecurityUpgradeViewModel + private val upgradeSecurity = mock() + + @Before + fun setUp() { + Dispatchers.setMain(UnconfinedTestDispatcher()) + underTest = SecurityUpgradeViewModel(upgradeSecurity) + } + + @After + fun tearDown() { + Dispatchers.resetMain() + } + + @Test + fun `test that upgrade security is invoked`() = runTest { + underTest.upgradeAccountSecurity() + verify(upgradeSecurity).invoke() + } +} \ No newline at end of file diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/UpgradeSecurity.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/UpgradeSecurity.kt new file mode 100644 index 00000000000..f91caa630a6 --- /dev/null +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/UpgradeSecurity.kt @@ -0,0 +1,12 @@ +package mega.privacy.android.domain.usecase + +/** + * Upgrade security use case + */ +fun interface UpgradeSecurity { + + /** + * Invoke + */ + suspend operator fun invoke() +} \ No newline at end of file From 3896221f1f0e4db2ec254d38035ffed5a857b7c4 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 13 Feb 2023 18:30:07 +0530 Subject: [PATCH 201/334] setSecureFlag changed to false --- app/src/qa/assets/featuretoggle/feature_flags.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/qa/assets/featuretoggle/feature_flags.json b/app/src/qa/assets/featuretoggle/feature_flags.json index 57ca03d12da..903fef89531 100644 --- a/app/src/qa/assets/featuretoggle/feature_flags.json +++ b/app/src/qa/assets/featuretoggle/feature_flags.json @@ -9,6 +9,6 @@ }, { "name": "SetSecureFlag", - "value": true + "value": false } ] \ No newline at end of file From 13f570ac98739fa96f50fe23dd7262f908299860 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 14 Feb 2023 11:17:26 +0530 Subject: [PATCH 202/334] Latest transifex strings downloaded --- .../app/di/featuretoggle/FeatureFlagModule.kt | 2 +- .../android/app/main/ManagerActivity.java | 14 +- .../NodeOptionsBottomSheetDialogFragment.java | 19 +- .../outgoing/OutgoingSharesViewModel.kt | 18 +- .../outgoing/model/OutgoingSharesState.kt | 4 + app/src/main/res/values/strings.xml | 485 ++++++++++++++++-- 6 files changed, 482 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt b/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt index 2e71d60bf55..38046e9b04f 100644 --- a/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/featuretoggle/FeatureFlagModule.kt @@ -73,7 +73,7 @@ abstract class FeatureFlagModule { /** * Provides [SetSecureFlag] implementation * - * @param filesRepository [FilesRepository] + * @param megaNodeRepository [MegaNodeRepository] * @return [SetSecureFlag] */ @Provides diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index a483ab5bb81..30f16276b51 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -1322,13 +1322,6 @@ protected void onCreate(Bundle savedInstanceState) { return null; })); - ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, state -> { - if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { - replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); - } - return Unit.INSTANCE; - }); - collectFlows(); viewModel.onGetNumUnreadUserAlerts().observe(this, this::updateNumUnreadUserAlerts); @@ -2556,6 +2549,13 @@ private void collectFlows() { } return Unit.INSTANCE; }); + + ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, state -> { + if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { + replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); + } + return Unit.INSTANCE; + }); } /** diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 51d7548d5c9..b533c985362 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -898,7 +898,11 @@ private void hideNodeActions() { TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); optionVerifyUser.setText(StringResourcesUtils.getString(R.string.shared_items_bottom_sheet_menu_verify_user, getMegaUserNameDB(user))); TextView nodeName = contentView.findViewById(R.id.node_name_text); - nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + if(nC.nodeComesFromIncoming(node)) { + nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); + } else { + nodeName.setText(node.getName()); + } optionVerifyUser.setVisibility(View.VISIBLE); optionVerifyUser.setOnClickListener(this); @@ -999,7 +1003,18 @@ public void onClick(View v) { case R.id.share_folder_option: outgoingSharesViewModel.callOpenShareDialog(node.getHandle()); - showShareFolderOptions(); + ViewExtensionsKt.collectFlow(requireActivity(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { + if (state.isOpenShareDialogSuccess()) { + showShareFolderOptions(); + } else { + if(state.getErrorMessage() != null) { + showSnackbar(requireActivity(), state.getErrorMessage()); + } else { + showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); + } + } + return Unit.INSTANCE; + }); break; case R.id.clear_share_option: diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 8a20536d57d..746114e3581 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -210,9 +210,21 @@ class OutgoingSharesViewModel @Inject constructor( * @param nodeHandle: [MegaNode] handle */ fun callOpenShareDialog(nodeHandle: Long) { - viewModelScope.launch { - getNodeByHandle(nodeHandle)?.let { megaNode -> - openShareDialog(megaNode) + kotlin.runCatching { + viewModelScope.launch { + if (!isInvalidHandle(nodeHandle)) { + getNodeByHandle(nodeHandle)?.let { megaNode -> + openShareDialog(megaNode) + } + } + } + }.onSuccess { + _state.update { + it.copy(isOpenShareDialogSuccess = true) + } + }.onFailure { error -> + _state.update { + it.copy(errorMessage = error.message) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index b326a7e9987..0ef0cc11f19 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -17,6 +17,8 @@ import nz.mega.sdk.MegaNode * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles + * @param isOpenShareDialogSuccess if openShareDialog API call is a success or failure + * @param errorMessage Error message to show on UI */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -29,6 +31,8 @@ data class OutgoingSharesState( val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodeHandles: List = emptyList(), + val isOpenShareDialogSuccess: Boolean = false, + val errorMessage: String? = null ) { /** diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index db8b404b1b0..4884853604e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -158,8 +158,6 @@ Connecting to the server Updating file list - - Confirm account Checking validation link @@ -170,8 +168,6 @@ Please log in to share with MEGA Your confirmation link is no longer valid. Your account may already be activated or you may have cancelled your registration. - - Name First name @@ -311,8 +307,6 @@ Folders are empty. - Touch to cancel - View transfers Most devices can’t download files greater than 4 GB. Your download will probably fail. @@ -1506,8 +1500,6 @@ You have received %1$s storage space for verifying your phone number. You have received %1$s storage space as your free registration bonus. - - Bonus expires in %1$d days Share folder @@ -2114,8 +2106,6 @@ Logged out Your account has been activated. Please log in. - - Please enter your password to confirm your account There’s no need to add your own email address @@ -2350,8 +2340,6 @@ Continue [A]Google Pay[/A] (subscription) - - [A]HUAWEI Pay[/A] (subscription) NEW @@ -2863,8 +2851,6 @@ Select messages Failed - - Your transfers have been interrupted. Upgrade your account or wait %s to continue. Transfer quota exceeded @@ -3214,10 +3200,6 @@ Months Years - - [B]%1$s[/B] %2$s - - [B]%1$s %2$s[/B], %3$s Upload in progress, 1 file pending @@ -3583,22 +3565,6 @@ Payment methods Proceed - - Pro Lite monthly - - Pro Lite yearly - - Pro I monthly - - Pro I yearly - - Pro II monthly - - Pro II yearly - - Pro III monthly - - Pro III yearly Remove as host @@ -3933,8 +3899,6 @@ Start chatting now Chat securely and privately, with anyone and on any device, knowing that no one can read your chats, not even MEGA. - - Archive meeting Start meeting now @@ -4027,8 +3991,6 @@ You’ve already added all your contacts to this chat. If you want to add more participants, first invite them to your contact list. Saved image to your device gallery - - [A]Renewal date:[/A] [B]%s[/B] Enter album name @@ -4166,29 +4128,458 @@ [A]%s [/A][B]invited you to a meeting scheduled for:[/B] - [A]%s [/A][B]cancelled the meeting scheduled for:[/B] + [A]%s cancelled[/A][B] the meeting scheduled for:[/B] - [A]%s [/A][B]updated the meeting name from “%s” to [/B]“[A]%s[/A]“ + [A]%s updated the meeting name[/A][B] from “%s” to [/B]“[A]%s[/A]” - [A]%s [/A][B]updated the meeting date[/B] + [A]%s updated[/A][B] the meeting date[/B] - [A]%s [/A][B]updated the meeting time[/B] + [A]%s updated[/A][B] the meeting time[/B] - [A]%s [/A][B]updated the meeting description[/B] + [A]%s updated[/A][B] the meeting description[/B] - [A]%s [/A][B]updated the meeting details scheduled for:[/B] + [A]%s updated[/A][B] the meeting details scheduled for:[/B] - Access Denied + Access denied You denied MEGA access to your device’s storage and media files. If you’d like to continue sharing, allow MEGA permission. Allow permission Don’t allow + + Occurrences + + Occurs daily + + Occurs weekly + + Occurs monthly + + See more occurrences + + %s monthly + + %s daily + + [A]%s [/A][B]invited you to a recurring meeting scheduled for:[/B] + + [A]%s updated[/A][B] the recurring meeting description[/B] + + [A]%s updated[/A][B] the recurring meeting details scheduled for:[/B] + + [A]%s updated[/A][B] an occurrence to:[/B] + + [A]%s updated[/A][B] the recurring meeting time[/B] + + [A]%s updated[/A][B] the recurring meeting frequency[/B] + + [A]%s cancelled[/A][B] the meeting and all its occurrences[/B] + + [A]%s cancelled[/A][B] the occurrence scheduled for:[/B] + + This link hasn’t been generated with the account you’re currently logged into. Log in to the account related to this link to verify your email address. + + Unable to update email address + + Select album cover + + Album cover updated + + %1$s from %2$s to %3$s + + Everyday effective %1$s from %2$s to %3$s + + Everyday effective %1$s until %2$s from %3$s to %4$s + + + %1$s every week effective %3$s until %4$s from %5$s to %6$s + %1$s every %2$d weeks effective %3$s until %4$s from %5$s to %6$s + + + + %1$s every week effective %3$s from %4$s to %5$s + %1$s every %2$d weeks effective %3$s from %4$s to %5$s + + + + %1$s and %2$s every week effective %4$s until %5$s from %6$s to %7$s + %1$s and %2$s every %3$d weeks effective %4$s until %5$s from %6$s to %7$s + + + + %1$s and %2$s every week effective %4$s from %5$s to %6$s + %1$s and %2$s every %3$d weeks effective %4$s from %5$s to %6$s + + + + Day %1$d of every month effective %3$s until %4$s from %5$s to %6$s + Day %1$d of every %2$d months effective %3$s until %4$s from %5$s to %6$s + + + + Day %1$d of every month effective %3$s from %4$s to %5$s + Day %1$d of every %2$d months effective %3$s from %4$s to %5$s + + + + First Monday of every month effective %2$s until %3$s from %4$s to %5$s + First Monday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Monday of every month effective %2$s from %3$s to %4$s + First Monday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Monday of every month effective %2$s until %3$s from %4$s to %5$s + Second Monday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Monday of every month effective %2$s from %3$s to %4$s + Second Monday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Monday of every month effective %2$s until %3$s from %4$s to %5$s + Third Monday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Monday of every month effective %2$s from %3$s to %4$s + Third Monday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Monday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Monday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Monday of every month effective %2$s from %3$s to %4$s + Fourth Monday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Monday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Monday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Monday of every month effective %2$s from %3$s to %4$s + Fifth Monday of every %1$d months effective %2$s from %3$s to %4$s + + + + First Tuesday of every month effective %2$s until %3$s from %4$s to %5$s + First Tuesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Tuesday of every month effective %2$s from %3$s to %4$s + First Tuesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Tuesday of every month effective %2$s until %3$s from %4$s to %5$s + Second Tuesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Tuesday of every month effective %2$s from %3$s to %4$s + Second Tuesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Tuesday of every month effective %2$s until %3$s from %4$s to %5$s + Third Tuesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Tuesday of every month effective %2$s from %3$s to %4$s + Third Tuesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Tuesday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Tuesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Tuesday of every month effective %2$s from %3$s to %4$s + Fourth Tuesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Tuesday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Tuesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Tuesday of every month effective %2$s from %3$s to %4$s + Fifth Tuesday of every %1$d months effective %2$s from %3$s to %4$s + + + + First Wednesday of every month effective %2$s until %3$s from %4$s to %5$s + First Wednesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Wednesday of every month effective %2$s from %3$s to %4$s + First Wednesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Wednesday of every month effective %2$s until %3$s from %4$s to %5$s + Second Wednesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Wednesday of every month effective %2$s from %3$s to %4$s + Second Wednesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Wednesday of every month effective %2$s until %3$s from %4$s to %5$s + Third Wednesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Wednesday of every month effective %2$s from %3$s to %4$s + Third Wednesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Wednesday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Wednesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Wednesday of every month effective %2$s from %3$s to %4$s + Fourth Wednesday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Wednesday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Wednesday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Wednesday of every month effective %2$s from %3$s to %4$s + Fifth Wednesday of every %1$d months effective %2$s from %3$s to %4$s + + + + First Thursday of every month effective %2$s until %3$s from %4$s to %5$s + First Thursday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Thursday of every month effective %2$s from %3$s to %4$s + First Thursday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Thursday of every month effective %2$s until %3$s from %4$s to %5$s + Second Thursday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Thursday of every month effective %2$s from %3$s to %4$s + Second Thursday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Thursday of every month effective %2$s until %3$s from %4$s to %5$s + Third Thursday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Thursday of every month effective %2$s from %3$s to %4$s + Third Thursday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Thursday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Thursday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Thursday of every month effective %2$s from %3$s to %4$s + Fourth Thursday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Thursday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Thursday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Thursday of every month effective %2$s from %3$s to %4$s + Fifth Thursday of every %1$d months effective %2$s from %3$s to %4$s + + + + First Friday of every month effective %2$s until %3$s from %4$s to %5$s + First Friday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Friday of every month effective %2$s from %3$s to %4$s + First Friday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Friday of every month effective %2$s until %3$s from %4$s to %5$s + Second Friday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Friday of every month effective %2$s from %3$s to %4$s + Second Friday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Friday of every month effective %2$s until %3$s from %4$s to %5$s + Third Friday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Friday of every month effective %2$s from %3$s to %4$s + Third Friday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Friday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Friday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Friday of every month effective %2$s from %3$s to %4$s + Fourth Friday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Friday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Friday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Friday of every month effective %2$s from %3$s to %4$s + Fifth Friday of every %1$d months effective %2$s from %3$s to %4$s + + + + First Saturday of every month effective %2$s until %3$s from %4$s to %5$s + First Saturday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Saturday of every month effective %2$s from %3$s to %4$s + First Saturday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Saturday of every month effective %2$s until %3$s from %4$s to %5$s + Second Saturday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Saturday of every month effective %2$s from %3$s to %4$s + Second Saturday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Saturday of every month effective %2$s until %3$s from %4$s to %5$s + Third Saturday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Saturday of every month effective %2$s from %3$s to %4$s + Third Saturday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Saturday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Saturday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Saturday of every month effective %2$s from %3$s to %4$s + Fourth Saturday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Saturday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Saturday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Saturday of every month effective %2$s from %3$s to %4$s + Fifth Saturday of every %1$d months effective %2$s from %3$s to %4$s + + + + First Sunday of every month effective %2$s until %3$s from %4$s to %5$s + First Sunday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + First Sunday of every month effective %2$s from %3$s to %4$s + First Sunday of every %1$d months effective %2$s from %3$s to %4$s + + + + Second Sunday of every month effective %2$s until %3$s from %4$s to %5$s + Second Sunday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Second Sunday of every month effective %2$s from %3$s to %4$s + Second Sunday of every %1$d months effective %2$s from %3$s to %4$s + + + + Third Sunday of every month effective %2$s until %3$s from %4$s to %5$s + Third Sunday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Third Sunday of every month effective %2$s from %3$s to %4$s + Third Sunday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fourth Sunday of every month effective %2$s until %3$s from %4$s to %5$s + Fourth Sunday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fourth Sunday of every month effective %2$s from %3$s to %4$s + Fourth Sunday of every %1$d months effective %2$s from %3$s to %4$s + + + + Fifth Sunday of every month effective %2$s until %3$s from %4$s to %5$s + Fifth Sunday of every %1$d months effective %2$s until %3$s from %4$s to %5$s + + + + Fifth Sunday of every month effective %2$s from %3$s to %4$s + Fifth Sunday of every %1$d months effective %2$s from %3$s to %4$s + + Security upgrade + Your account’s security is now being upgraded. This will happen only once. If you’ve seen this message for this account before, tap Cancel. - You’re currently sharing the following folders: %s - + + + You’re currently sharing the following folder: %s + You’re currently sharing the following folders: %s + [Undecrypted file] From e711885dddbb8a6517a446fc293f7db049a39277 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 14 Feb 2023 12:28:38 +0530 Subject: [PATCH 203/334] Multiple folder names displayed on security upgrade dialog --- .../SecurityUpgradeDialogFragment.kt | 4 ++-- .../fingerprintauth/SecurityUpgradeDialogView.kt | 16 ++++++++++------ 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt index b2edf33400a..3591e9ff805 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt @@ -33,11 +33,11 @@ class SecurityUpgradeDialogFragment : DialogFragment() { MaterialAlertDialogBuilder(requireContext()).setView( ComposeView(requireContext()).apply { setContent { - val nodeName = arguments?.getString("nodeName", "Default") as String + val nodeName = arguments?.getStringArrayList("nodeName") as List val mode by getThemeMode() .collectAsStateWithLifecycle(initialValue = ThemeMode.System) AndroidTheme(isDark = mode.isDarkMode()) { - SecurityUpgradeDialogView(folderName = nodeName, onCancelClick = { + SecurityUpgradeDialogView(folderNames = nodeName, onCancelClick = { requireActivity().finishAffinity() }, onOkClick = { securityUpgradeViewModel.upgradeAccountSecurity() diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt index 96e9afe03cb..2ba5c4a08dd 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt @@ -1,5 +1,6 @@ package mega.privacy.android.app.presentation.fingerprintauth +import android.text.TextUtils import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -16,18 +17,20 @@ import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import mega.privacy.android.app.R -import mega.privacy.android.presentation.theme.body2 -import mega.privacy.android.presentation.theme.jade_300 -import mega.privacy.android.presentation.theme.subtitle1 +import mega.privacy.android.core.ui.theme.body2 +import mega.privacy.android.core.ui.theme.jade_300 +import mega.privacy.android.core.ui.theme.subtitle1 /** * Security upgrade dialog body @@ -36,9 +39,10 @@ import mega.privacy.android.presentation.theme.subtitle1 * @param onOkClick : Ok button click listener * @param onCancelClick : Cancel button click listener */ +@OptIn(ExperimentalComposeUiApi::class) @Composable fun SecurityUpgradeDialogView( - folderName: String, + folderNames: List, onOkClick: () -> Unit, onCancelClick: () -> Unit, ) { @@ -83,8 +87,8 @@ fun SecurityUpgradeDialogView( Spacer(Modifier.height(20.dp)) Text(modifier = Modifier.testTag("SharedNodeInfo"), - text = stringResource(id = R.string.shared_items_security_upgrade_dialog_node_sharing_info, - folderName), + text = pluralStringResource(id = R.plurals.shared_items_security_upgrade_dialog_node_sharing_info, + folderNames.size, TextUtils.join(", ", folderNames)), style = body2.copy(textAlign = TextAlign.Center), color = if (MaterialTheme.colors.isLight) { Color.Black From 818ad2432c142b365296f21eaf1c017a40afceed Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 14 Feb 2023 13:36:44 +0530 Subject: [PATCH 204/334] Removed repetitive collection of flow --- .../privacy/android/app/main/ManagerActivity.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 30f16276b51..d48d8f41b58 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -2490,6 +2490,11 @@ private void collectFlows() { }); ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { + + if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { + replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); + } + updateInboxSectionVisibility(managerState.getHasInboxChildren()); stopUploadProcessAndSendBroadcast(managerState.getShouldStopCameraUpload(), managerState.getShouldSendCameraBroadcastEvent()); if (managerState.getNodeUpdateReceived()) { @@ -2549,13 +2554,6 @@ private void collectFlows() { } return Unit.INSTANCE; }); - - ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, state -> { - if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { - replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); - } - return Unit.INSTANCE; - }); } /** From eff882f22aff84ed621f48582200b03c969873a5 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 14 Feb 2023 14:20:25 +0530 Subject: [PATCH 205/334] API error removed & generic message is displayed --- .../NodeOptionsBottomSheetDialogFragment.java | 6 +----- .../presentation/shares/outgoing/OutgoingSharesViewModel.kt | 4 ++-- .../shares/outgoing/model/OutgoingSharesState.kt | 2 -- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index b533c985362..8b0ed139dd2 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -1007,11 +1007,7 @@ public void onClick(View v) { if (state.isOpenShareDialogSuccess()) { showShareFolderOptions(); } else { - if(state.getErrorMessage() != null) { - showSnackbar(requireActivity(), state.getErrorMessage()); - } else { - showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); - } + showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); } return Unit.INSTANCE; }); diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 746114e3581..40d63801c62 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -222,9 +222,9 @@ class OutgoingSharesViewModel @Inject constructor( _state.update { it.copy(isOpenShareDialogSuccess = true) } - }.onFailure { error -> + }.onFailure { _state.update { - it.copy(errorMessage = error.message) + it.copy(isOpenShareDialogSuccess = false) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index 0ef0cc11f19..cccd55fa233 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -18,7 +18,6 @@ import nz.mega.sdk.MegaNode * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles * @param isOpenShareDialogSuccess if openShareDialog API call is a success or failure - * @param errorMessage Error message to show on UI */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -32,7 +31,6 @@ data class OutgoingSharesState( val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodeHandles: List = emptyList(), val isOpenShareDialogSuccess: Boolean = false, - val errorMessage: String? = null ) { /** From 72c806355915ace2e7da1a59233b01ea73dd521c Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 14 Feb 2023 14:57:14 +0530 Subject: [PATCH 206/334] Moved flow collection into onViewCreated. --- .../NodeOptionsBottomSheetDialogFragment.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 8b0ed139dd2..22ef0afd792 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -718,7 +718,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); if(nC.nodeComesFromIncoming(node)) { - ViewExtensionsKt.collectFlow(requireActivity(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { + ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { @@ -728,13 +728,20 @@ && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { return Unit.INSTANCE; }); } else { - ViewExtensionsKt.collectFlow(requireActivity(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { + ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() && mMode == SHARED_ITEMS_MODE && isNodeUnverified(state.getUnVerifiedOutgoingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedOutgoingShares()); hideNodeActions(); } + + if (state.isOpenShareDialogSuccess()) { + showShareFolderOptions(); + } else { + showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); + } + return Unit.INSTANCE; }); } @@ -1003,14 +1010,6 @@ public void onClick(View v) { case R.id.share_folder_option: outgoingSharesViewModel.callOpenShareDialog(node.getHandle()); - ViewExtensionsKt.collectFlow(requireActivity(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (state.isOpenShareDialogSuccess()) { - showShareFolderOptions(); - } else { - showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); - } - return Unit.INSTANCE; - }); break; case R.id.clear_share_option: From 78596433cd227c4e3f122266c168703de115cb52 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 15 Feb 2023 02:14:57 +1300 Subject: [PATCH 207/334] AND-15672 NodeOptionsBottomSheetViewModel created specifically for NodeOptionsBottomSheetDialogFragment --- .../NodeOptionsBottomSheetDialogFragment.java | 20 +++--- .../NodeOptionsBottomSheetState.kt | 12 ++++ .../NodeOptionsBottomSheetViewModel.kt | 72 +++++++++++++++++++ .../outgoing/OutgoingSharesViewModel.kt | 27 ------- .../outgoing/model/OutgoingSharesState.kt | 2 - .../NodeOptionsBottomSheetViewModelTest.kt | 61 ++++++++++++++++ .../outgoing/OutgoingSharesViewModelTest.kt | 4 -- 7 files changed, 157 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt create mode 100644 app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt create mode 100644 app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 22ef0afd792..23fcb7d7e88 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -175,6 +175,7 @@ public class NodeOptionsBottomSheetDialogFragment extends BaseBottomSheetDialogF private IncomingSharesViewModel incomingSharesViewModel; private OutgoingSharesViewModel outgoingSharesViewModel; + private NodeOptionsBottomSheetViewModel nodeOptionsBottomSheetViewModel; public NodeOptionsBottomSheetDialogFragment(int mode) { if (mode >= DEFAULT_MODE && mode <= FAVOURITES_MODE) { @@ -192,6 +193,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class); incomingSharesViewModel = new ViewModelProvider(requireActivity()).get(IncomingSharesViewModel.class); outgoingSharesViewModel = new ViewModelProvider(requireActivity()).get(OutgoingSharesViewModel.class); + nodeOptionsBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(NodeOptionsBottomSheetViewModel.class); } @Nullable @@ -735,16 +737,18 @@ && isNodeUnverified(state.getUnVerifiedOutgoingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedOutgoingShares()); hideNodeActions(); } - - if (state.isOpenShareDialogSuccess()) { - showShareFolderOptions(); - } else { - showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); - } - return Unit.INSTANCE; }); } + + ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), nodeOptionsBottomSheetViewModel.getState(), Lifecycle.State.STARTED, state -> { + if (state.isOpenShareDialogSuccess()) { + showShareFolderOptions(); + } else { + showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); + } + return Unit.INSTANCE; + }); } @Override @@ -1009,7 +1013,7 @@ public void onClick(View v) { break; case R.id.share_folder_option: - outgoingSharesViewModel.callOpenShareDialog(node.getHandle()); + nodeOptionsBottomSheetViewModel.callOpenShareDialog(node.getHandle()); break; case R.id.clear_share_option: diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt new file mode 100644 index 00000000000..b9873472936 --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt @@ -0,0 +1,12 @@ +package mega.privacy.android.app.modalbottomsheet + +/** + * Node options UI state + * + * @param currentNodeHandle Node handle of the current node for which bottom sheet dialog is opened + * @param isOpenShareDialogSuccess if openShareDialog API call is a success or failure + */ +data class NodeOptionsBottomSheetState( + val currentNodeHandle: Long = -1L, + val isOpenShareDialogSuccess: Boolean = false, +) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt new file mode 100644 index 00000000000..cf70e2ad2ac --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt @@ -0,0 +1,72 @@ +package mega.privacy.android.app.modalbottomsheet + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import mega.privacy.android.app.domain.usecase.GetNodeByHandle +import mega.privacy.android.app.domain.usecase.OpenShareDialog +import nz.mega.sdk.MegaApiJava +import nz.mega.sdk.MegaNode +import javax.inject.Inject + +/** + * View model associated with [NodeOptionsBottomSheetDialogFragment] + */ +@HiltViewModel +class NodeOptionsBottomSheetViewModel @Inject constructor( + private val openShareDialog: OpenShareDialog, + private val getNodeByHandle: GetNodeByHandle, +) : ViewModel() { + + /** + * Private UI state + */ + private val _state = MutableStateFlow(NodeOptionsBottomSheetState()) + + /** + * Public Ui state + */ + val state: StateFlow = _state + + /** + * Check if the handle is valid or not + * + * @param handle + * @return true if the handle is invalid + */ + private suspend fun isInvalidHandle(handle: Long = _state.value.currentNodeHandle): Boolean { + return handle + .takeUnless { it == -1L || it == MegaApiJava.INVALID_HANDLE } + ?.let { getNodeByHandle(it) == null } + ?: true + } + + /** + * Calls OpenShareDialog use case to create crypto key for sharing + * + * @param nodeHandle: [MegaNode] handle + */ + fun callOpenShareDialog(nodeHandle: Long) { + kotlin.runCatching { + viewModelScope.launch { + if (!isInvalidHandle(nodeHandle)) { + getNodeByHandle(nodeHandle)?.let { megaNode -> + openShareDialog(megaNode) + } + } + } + }.onSuccess { + _state.update { + it.copy(isOpenShareDialogSuccess = true) + } + }.onFailure { + _state.update { + it.copy(isOpenShareDialogSuccess = false) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 40d63801c62..f39e518dbb3 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates -import mega.privacy.android.app.domain.usecase.OpenShareDialog import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.model.OutgoingSharesState import mega.privacy.android.domain.usecase.GetCloudSortOrder @@ -37,7 +36,6 @@ class OutgoingSharesViewModel @Inject constructor( monitorNodeUpdates: MonitorNodeUpdates, private val getFeatureFlagValue: GetFeatureFlagValue, private val getUnverifiedOutgoingShares: GetUnverifiedOutgoingShares, - private val openShareDialog: OpenShareDialog, ) : ViewModel() { /** private UI state */ @@ -203,29 +201,4 @@ class OutgoingSharesViewModel @Inject constructor( it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) } } - - /** - * Calls OpenShareDialog use case to create crypto key for sharing - * - * @param nodeHandle: [MegaNode] handle - */ - fun callOpenShareDialog(nodeHandle: Long) { - kotlin.runCatching { - viewModelScope.launch { - if (!isInvalidHandle(nodeHandle)) { - getNodeByHandle(nodeHandle)?.let { megaNode -> - openShareDialog(megaNode) - } - } - } - }.onSuccess { - _state.update { - it.copy(isOpenShareDialogSuccess = true) - } - }.onFailure { - _state.update { - it.copy(isOpenShareDialogSuccess = false) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index cccd55fa233..b326a7e9987 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -17,7 +17,6 @@ import nz.mega.sdk.MegaNode * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles - * @param isOpenShareDialogSuccess if openShareDialog API call is a success or failure */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, @@ -30,7 +29,6 @@ data class OutgoingSharesState( val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodeHandles: List = emptyList(), - val isOpenShareDialogSuccess: Boolean = false, ) { /** diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt new file mode 100644 index 00000000000..8b715e1556d --- /dev/null +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt @@ -0,0 +1,61 @@ +package test.mega.privacy.android.app.presentation + +import app.cash.turbine.test +import com.google.common.truth.Truth +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain +import mega.privacy.android.app.domain.usecase.GetNodeByHandle +import mega.privacy.android.app.domain.usecase.OpenShareDialog +import mega.privacy.android.app.modalbottomsheet.NodeOptionsBottomSheetViewModel +import org.junit.Before +import org.junit.Test +import org.mockito.kotlin.mock +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@ExperimentalCoroutinesApi +class NodeOptionsBottomSheetViewModelTest { + + private lateinit var underTest: NodeOptionsBottomSheetViewModel + private val getNodeByHandle = mock() + private val openShareDialog = mock() + + @Before + fun setUp() { + Dispatchers.setMain(UnconfinedTestDispatcher()) + underTest = NodeOptionsBottomSheetViewModel(openShareDialog, getNodeByHandle) + } + + @Test + fun `test that initial state is returned`() = runTest { + underTest.state.test { + val initial = awaitItem() + Truth.assertThat(initial.currentNodeHandle).isEqualTo(-1L) + Truth.assertThat(initial.isOpenShareDialogSuccess).isEqualTo(false) + } + } + + @Test + fun `test that open share dialog success result gets updated in state`() = runTest { + underTest.callOpenShareDialog(3829183L) + underTest.state.runCatching { + this.test { + assertTrue(awaitItem().isOpenShareDialogSuccess) + } + } + } + + @Test + fun `test that open share dialog failure result gets updated in state`() = runTest { + underTest.callOpenShareDialog(-1) + underTest.state.runCatching { + this.test { + assertFalse(awaitItem().isOpenShareDialogSuccess) + } + } + } + +} \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index a23deb884d4..a97d38b03e3 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -12,7 +12,6 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode -import mega.privacy.android.app.domain.usecase.OpenShareDialog import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel import mega.privacy.android.domain.entity.ShareData @@ -66,8 +65,6 @@ class OutgoingSharesViewModelTest { onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) } - private val openShareDialog = mock() - @Before fun setUp() { Dispatchers.setMain(UnconfinedTestDispatcher()) @@ -84,7 +81,6 @@ class OutgoingSharesViewModelTest { monitorNodeUpdates, getFeatureFlagValue, getUnverifiedOutgoingShares, - openShareDialog, ) } From 6ecfb36a664d0ce2be0e34561b2cedf8de69c7b9 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 17 Feb 2023 19:05:30 +1300 Subject: [PATCH 208/334] MandatoryFingerprintVerification feature flag removed from code --- .../android/app/featuretoggle/AppFeatures.kt | 6 -- .../android/app/main/ManagerActivity.java | 3 +- .../app/main/adapters/MegaNodeAdapter.java | 22 ++--- .../AuthenticityCredentialsViewModel.kt | 12 --- .../model/AuthenticityCredentialsState.kt | 2 - .../view/AuthenticityCredentialsView.kt | 80 +++++++------------ .../presentation/manager/ManagerViewModel.kt | 32 ++------ .../manager/model/ManagerState.kt | 3 +- .../presentation/search/SearchViewModel.kt | 54 ++++++++++--- .../shares/incoming/IncomingSharesFragment.kt | 1 - .../incoming/IncomingSharesViewModel.kt | 16 ---- .../shares/links/LinksFragment.kt | 1 - .../shares/links/LinksViewModel.kt | 23 ------ .../shares/outgoing/OutgoingSharesFragment.kt | 1 - .../outgoing/OutgoingSharesViewModel.kt | 16 ---- .../AuthenticityCredentialsViewModelTest.kt | 11 --- .../AuthenticityCredentialsViewTest.kt | 3 - .../manager/ManagerViewModelTest.kt | 10 +-- .../incoming/IncomingSharesViewModelTest.kt | 10 --- .../shares/links/LinksViewModelTest.kt | 10 --- .../outgoing/OutgoingSharesViewModelTest.kt | 10 --- 21 files changed, 89 insertions(+), 237 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt b/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt index c134804cb88..02698d12616 100644 --- a/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt +++ b/app/src/main/java/mega/privacy/android/app/featuretoggle/AppFeatures.kt @@ -26,12 +26,6 @@ enum class AppFeatures(override val description: String, private val defaultValu */ SetSecureFlag("Sets the secure flag value for MegaApi", false), - /** - * Indicates if the user is cryptographically secure - */ - MandatoryFingerprintVerification("Indicates if mandatory fingerprint verification needs to be done", - false), - /** * User albums toggle */ diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index d48d8f41b58..1a84086c6cb 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -357,6 +357,7 @@ import mega.privacy.android.app.presentation.clouddrive.FileBrowserViewModel; import mega.privacy.android.app.presentation.fileinfo.FileInfoActivity; import mega.privacy.android.app.presentation.fingerprintauth.SecurityUpgradeDialogFragment; +import mega.privacy.android.app.presentation.folderlink.FolderLinkActivity; import mega.privacy.android.app.presentation.inbox.InboxFragment; import mega.privacy.android.app.presentation.inbox.InboxViewModel; import mega.privacy.android.app.presentation.login.LoginActivity; @@ -2509,7 +2510,7 @@ private void collectFlows() { } // Update pending actions badge on bottom navigation menu - if (managerState.isMandatoryFingerprintVerificationNeeded() && managerState.getPendingActionsCount() > 0) { + if (managerState.getPendingActionsCount() > 0) { BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); View pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_pending_actions_badge, menuView, false); sharedItemsView.addView(pendingActionsBadge); diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 1101a22b026..9694219e797 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -89,6 +89,7 @@ import mega.privacy.android.app.main.DrawerItem; import mega.privacy.android.app.main.FolderLinkActivity; import mega.privacy.android.app.main.ManagerActivity; +import mega.privacy.android.app.main.contactSharedFolder.ContactSharedFolderFragment; import mega.privacy.android.app.presentation.clouddrive.FileBrowserFragment; import mega.privacy.android.app.presentation.inbox.InboxFragment; import mega.privacy.android.app.presentation.rubbishbin.RubbishBinFragment; @@ -97,13 +98,11 @@ import mega.privacy.android.app.presentation.shares.links.LinksFragment; import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesFragment; import mega.privacy.android.app.utils.ColorUtils; -import mega.privacy.android.app.utils.ContactUtil; import mega.privacy.android.app.utils.MegaNodeUtil; import mega.privacy.android.app.utils.NodeTakenDownDialogListener; import mega.privacy.android.app.utils.ThumbnailUtils; import mega.privacy.android.data.database.DatabaseHandler; import mega.privacy.android.data.model.MegaContactDB; -import mega.privacy.android.domain.entity.ShareData; import mega.privacy.android.domain.entity.SortOrder; import nz.mega.sdk.MegaApiAndroid; import nz.mega.sdk.MegaNode; @@ -149,7 +148,6 @@ public class MegaNodeAdapter extends RecyclerView.Adapter unverifiedIncomingNodeHandles = new HashSet<>(); private final Set unverifiedOutgoingNodeHandles = new HashSet<>(); - private boolean isMandatoryFingerprintVerificationNeeded; public static class ViewHolderBrowser extends RecyclerView.ViewHolder { @@ -242,8 +240,6 @@ else if (type == OUTGOING_SHARES_ADAPTER binding.listModeSwitch.setVisibility(type == LINKS_ADAPTER ? View.GONE : View.VISIBLE); - - setMediaDiscoveryVisibility(binding); } } @@ -434,7 +430,7 @@ void hideMultipleSelect() { } public void selectAll() { - for (int i = 0; i < nodes.size(); i ++) { + for (int i = 0; i < nodes.size(); i++) { selectedItems.put(i, true); notifyItemChanged(i); } @@ -446,7 +442,7 @@ public void clearSelections() { return; } - for (int i = 0; i < nodes.size(); i ++) { + for (int i = 0; i < nodes.size(); i++) { selectedItems.delete(i); notifyItemChanged(i); } @@ -1092,8 +1088,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - boolean hasUnverifiedNodes = isMandatoryFingerprintVerificationNeeded - && !unverifiedIncomingNodeHandles.isEmpty() + boolean hasUnverifiedNodes = !unverifiedIncomingNodeHandles.isEmpty() && unverifiedIncomingNodeHandles.contains(node.getHandle()); if (hasUnverifiedNodes) { showUnverifiedNodeUi(holder, true); @@ -1104,10 +1099,10 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } } else if (type == OUTGOING_SHARES_ADAPTER) { + holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - boolean hasUnverifiedNodes = isMandatoryFingerprintVerificationNeeded - && !unverifiedOutgoingNodeHandles.isEmpty() + boolean hasUnverifiedNodes = !unverifiedOutgoingNodeHandles.isEmpty() && unverifiedOutgoingNodeHandles.contains(node.getHandle()); if (hasUnverifiedNodes) { showUnverifiedNodeUi(holder, false); @@ -1205,7 +1200,6 @@ public int getItemCount() { @Override public int getItemViewType(int position) { return !nodes.isEmpty() && position == 0 - && type != FOLDER_LINK_ADAPTER && type != CONTACT_SHARED_FOLDER_ADAPTER && type != CONTACT_FILE_ADAPTER ? ITEM_VIEW_TYPE_HEADER @@ -1523,10 +1517,6 @@ public void onCancelClicked() { unHandledItem = -1; } - public void setMandatoryFingerprintVerificationValue(boolean isVerificationNeeded) { - this.isMandatoryFingerprintVerificationNeeded = isVerificationNeeded; - } - /** * Adds unverified incoming nodes to Set * diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt index 9b3726d2933..10afc13c18f 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt @@ -10,13 +10,11 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import mega.privacy.android.app.R -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.contact.authenticitycredendials.model.AuthenticityCredentialsState import mega.privacy.android.app.presentation.extensions.getErrorStringId import mega.privacy.android.domain.exception.MegaException import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetContactCredentials -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetMyCredentials import mega.privacy.android.domain.usecase.MonitorConnectivity import mega.privacy.android.domain.usecase.ResetCredentials @@ -41,7 +39,6 @@ class AuthenticityCredentialsViewModel @Inject constructor( private val verifyCredentials: VerifyCredentials, private val resetCredentials: ResetCredentials, monitorConnectivity: MonitorConnectivity, - private val getFeatureFlagValue: GetFeatureFlagValue, ) : ViewModel() { private val _state = MutableStateFlow(AuthenticityCredentialsState()) @@ -54,7 +51,6 @@ class AuthenticityCredentialsViewModel @Inject constructor( viewModelScope.launch { _state.update { it.copy(myAccountCredentials = getMyCredentials()) } } - getMandatoryFingerPrintVerificationFeatureFlag() } /** @@ -147,12 +143,4 @@ class AuthenticityCredentialsViewModel @Inject constructor( * Updates state after shown error. */ fun errorShown() = _state.update { it.copy(error = null) } - - private fun getMandatoryFingerPrintVerificationFeatureFlag() { - viewModelScope.launch { - _state.update { - it.copy(isMandatoryFingerPrintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt index 66bfe68dd11..86fcca4430a 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt @@ -10,7 +10,6 @@ import mega.privacy.android.domain.entity.contacts.AccountCredentials * @property isVerifyingCredentials True if is already verifying credentials, false otherwise. * @property myAccountCredentials [AccountCredentials.MyAccountCredentials]. * @property error String resource id for showing an error. - * @property isMandatoryFingerPrintVerificationNeeded Feature flag value for Mandatory fingerprint verification */ data class AuthenticityCredentialsState( val contactCredentials: AccountCredentials.ContactCredentials? = null, @@ -18,5 +17,4 @@ data class AuthenticityCredentialsState( val isVerifyingCredentials: Boolean = false, val myAccountCredentials: AccountCredentials.MyAccountCredentials? = null, val error: Int? = null, - val isMandatoryFingerPrintVerificationNeeded: Boolean = false, ) \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt index 3c648f93bdd..bbbe2ee4ad1 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt @@ -132,56 +132,44 @@ fun ContactCredentials( Box(modifier = Modifier .fillMaxSize() .background(color = if (MaterialTheme.colors.isLight) white else dark_grey)) { - val name = if (state.isMandatoryFingerPrintVerificationNeeded) { - state.contactCredentials?.name ?: "" - } else { - stringResource(id = R.string.label_contact_credentials, - state.contactCredentials?.name ?: "") - } Column { - if (state.isMandatoryFingerPrintVerificationNeeded) { - if (isBannerVisible) { - Box(modifier = Modifier - .testTag("CONTACT_VERIFICATION_BANNER_VIEW") - .fillMaxWidth() - .background(color = yellow_100), - contentAlignment = Alignment.CenterEnd) { - Text(modifier = Modifier.padding(start = 24.dp, - top = 14.dp, - bottom = 14.dp, - end = 48.dp), - style = MaterialTheme.typography.body2, - color = black, - text = stringResource(id = R.string.shared_items_verify_credentials_verify_person_banner_label)) + if (isBannerVisible) { + Box(modifier = Modifier + .testTag("CONTACT_VERIFICATION_BANNER_VIEW") + .fillMaxWidth() + .background(color = yellow_100), + contentAlignment = Alignment.CenterEnd) { + Text(modifier = Modifier.padding(start = 24.dp, + top = 14.dp, + bottom = 14.dp, + end = 48.dp), + style = MaterialTheme.typography.body2, + color = black, + text = stringResource(id = R.string.shared_items_verify_credentials_verify_person_banner_label)) - IconButton( - onClick = { isBannerVisible = false }, - modifier = Modifier.padding(start = 310.dp), - enabled = true, - content = { - Icon(painter = painterResource(id = R.drawable.ic_remove_chat_toolbar), - contentDescription = "") - } - ) - } + IconButton( + onClick = { isBannerVisible = false }, + modifier = Modifier.padding(start = 310.dp), + enabled = true, + content = { + Icon(painter = painterResource(id = R.drawable.ic_remove_chat_toolbar), + contentDescription = "") + } + ) } - - Text(modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 24.dp), - style = MaterialTheme.typography.body2, - color = if (MaterialTheme.colors.isLight) grey_alpha_087 else white_alpha_087, - text = if (state.isMandatoryFingerPrintVerificationNeeded) { - // This lint is expected for now. This will get removed later when more conditions are added to decide the text - stringResource(id = R.string.shared_items_verify_credentials_header_incoming) - } else { - stringResource(id = R.string.shared_items_verify_credentials_header_outgoing) - }) } + Text(modifier = Modifier.padding(start = 24.dp, top = 16.dp, end = 24.dp), + style = MaterialTheme.typography.body2, + color = if (MaterialTheme.colors.isLight) grey_alpha_087 else white_alpha_087, + text = stringResource(id = R.string.shared_items_verify_credentials_header_outgoing)) + Text(modifier = Modifier.padding(top = 19.dp, start = 72.dp, end = 72.dp), style = MaterialTheme.typography.subtitle1, color = if (MaterialTheme.colors.isLight) grey_alpha_087 else white_alpha_087, - text = name) + text = stringResource(id = R.string.label_contact_credentials, + state.contactCredentials?.name ?: "")) Text(modifier = Modifier.padding(start = 72.dp, end = 72.dp), style = MaterialTheme.typography.body2, @@ -219,14 +207,8 @@ fun ContactCredentials( @Composable fun MyCredentials(state: AuthenticityCredentialsState) { - var credentialsExplanation = stringResource(id = R.string.authenticity_credentials_explanation) - var yourCredentials = stringResource(id = R.string.label_your_credentials) - if (state.isMandatoryFingerPrintVerificationNeeded) { - credentialsExplanation = - stringResource(id = R.string.shared_items_verify_credentials_information) - yourCredentials = - stringResource(id = R.string.shared_items_verify_credentials_my_credentials) - } + val credentialsExplanation = stringResource(id = R.string.authenticity_credentials_explanation) + val yourCredentials = stringResource(id = R.string.label_your_credentials) Text(modifier = Modifier.padding(start = 24.dp, top = 32.dp, end = 24.dp), style = MaterialTheme.typography.body2, diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index da11349e521..055e151e556 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -20,7 +20,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import mega.privacy.android.app.domain.usecase.GetInboxNode import mega.privacy.android.app.domain.usecase.GetPrimarySyncHandle -import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates @@ -56,9 +55,9 @@ import mega.privacy.android.domain.usecase.MonitorContactRequestUpdates import mega.privacy.android.domain.usecase.MonitorMyAvatarFile import mega.privacy.android.domain.usecase.MonitorStorageStateEvent import mega.privacy.android.domain.usecase.SendStatisticsMediaDiscovery +import nz.mega.sdk.MegaEvent import mega.privacy.android.domain.usecase.billing.GetActiveSubscription import mega.privacy.android.domain.usecase.viewtype.MonitorViewType -import nz.mega.sdk.MegaEvent import nz.mega.sdk.MegaNode import nz.mega.sdk.MegaUser import nz.mega.sdk.MegaUserAlert @@ -71,7 +70,6 @@ import javax.inject.Inject * * @param monitorNodeUpdates Monitor global node updates * @param monitorGlobalUpdates Monitor global updates - * @param getRubbishBinChildrenNode Fetch the rubbish bin nodes * @param monitorContactRequestUpdates * @param getInboxNode * @param getNumUnreadUserAlerts @@ -83,12 +81,14 @@ import javax.inject.Inject * @param monitorStorageStateEvent monitor global storage state changes * @param monitorViewType * @param getCloudSortOrder + * @param getFeatureFlagValue + * @param getUnverifiedIncomingShares + * @param getUnverifiedOutgoingShares */ @HiltViewModel class ManagerViewModel @Inject constructor( monitorNodeUpdates: MonitorNodeUpdates, private val monitorGlobalUpdates: MonitorGlobalUpdates, - private val getRubbishBinChildrenNode: GetRubbishBinChildrenNode, monitorContactRequestUpdates: MonitorContactRequestUpdates, private val getInboxNode: GetInboxNode, private val getNumUnreadUserAlerts: GetNumUnreadUserAlerts, @@ -150,7 +150,6 @@ class ManagerViewModel @Inject constructor( ) init { - viewModelScope.launch { monitorNodeUpdates().collect { val nodeList = it.changes.keys.toList() @@ -168,9 +167,8 @@ class ManagerViewModel @Inject constructor( } viewModelScope.launch { - _state.update { - it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) - } + val showSyncSection = getFeatureFlagValue(AppFeatures.AndroidSync) + _state.value = _state.value.copy(showSyncSection = showSyncSection) } viewModelScope.launch { @@ -189,7 +187,7 @@ class ManagerViewModel @Inject constructor( viewModelScope.launch { updateGlobalEvents.collect { megaEvent -> - if (_state.value.isMandatoryFingerprintVerificationNeeded && megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { + if (megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { _state.update { it.copy(shouldAlertUserAboutSecurityUpgrade = true) } @@ -205,12 +203,6 @@ class ManagerViewModel @Inject constructor( private val _updates = monitorGlobalUpdates() .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) - /** - * Monitor global node updates - */ - private val _updateNodes = monitorNodeUpdates() - .shareIn(viewModelScope, SharingStarted.WhileSubscribed()) - /** * Monitor contact requests */ @@ -263,16 +255,6 @@ class ManagerViewModel @Inject constructor( .map { Event(it) } .asLiveData() - /** - * Update Rubbish Nodes when a node update callback happens - */ - val updateRubbishBinNodes: LiveData>> = - _updateNodes - .also { Timber.d("onRubbishNodesUpdate") } - .mapNotNull { getRubbishBinChildrenNode(_state.value.rubbishBinParentHandle) } - .map { Event(it) } - .asLiveData() - /** * On my avatar file changed */ diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index 8dca9970c14..ed40db8756e 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -12,9 +12,9 @@ package mega.privacy.android.app.presentation.manager.model * @param shouldStopCameraUpload camera upload should be stopped or not * @param shouldSendCameraBroadcastEvent broadcast event should be sent or not * @param nodeUpdateReceived one-off event to notify UI that a node update occurred - * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param pendingActionsCount Pending actions count * @param shouldAlertUserAboutSecurityUpgrade Boolean to decide whether to display security upgrade dialog or not + * @param showSyncSection Boolean to show sync section */ data class ManagerState( val rubbishBinParentHandle: Long = -1L, @@ -26,7 +26,6 @@ data class ManagerState( val shouldStopCameraUpload: Boolean = false, val shouldSendCameraBroadcastEvent: Boolean = false, val nodeUpdateReceived: Boolean = false, - val isMandatoryFingerprintVerificationNeeded: Boolean = false, val pendingActionsCount: Int = 0, val shouldAlertUserAboutSecurityUpgrade: Boolean = false, ) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt index b991dcf40cb..52e378a5609 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt @@ -16,7 +16,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import mega.privacy.android.app.domain.usecase.GetRootFolder import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.fragments.homepage.Event import mega.privacy.android.app.main.DrawerItem import mega.privacy.android.app.presentation.manager.model.SharesTab @@ -25,12 +24,13 @@ import mega.privacy.android.app.search.usecase.SearchNodesUseCase import mega.privacy.android.app.search.usecase.SearchNodesUseCase.Companion.TYPE_GENERAL import mega.privacy.android.data.mapper.SortOrderIntMapper import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue +import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.RootNodeExists import nz.mega.sdk.MegaApiJava.INVALID_HANDLE import nz.mega.sdk.MegaCancelToken import nz.mega.sdk.MegaNode import timber.log.Timber +import java.util.Stack import javax.inject.Inject /** @@ -40,6 +40,7 @@ import javax.inject.Inject * @param rootNodeExists Check if the root node exists * @param searchNodesUseCase Perform a search request * @param getCloudSortOrder Get the Cloud Sort Order + * @param getSearchParentNodeHandle Get parent node for current node */ @HiltViewModel class SearchViewModel @Inject constructor( @@ -48,7 +49,7 @@ class SearchViewModel @Inject constructor( private val getRootFolder: GetRootFolder, private val searchNodesUseCase: SearchNodesUseCase, private val getCloudSortOrder: GetCloudSortOrder, - private val getFeatureFlagValue: GetFeatureFlagValue, + private val getSearchParentNodeHandle: GetParentNodeHandle, ) : ViewModel() { /** @@ -68,6 +69,11 @@ class SearchViewModel @Inject constructor( */ val stateLiveData = _state.map { Event(it) }.asLiveData() + /** + * Stack to maintain folder navigation clicks + */ + private val lastPositionStack: Stack = Stack() + /** * Monitor global node updates */ @@ -78,10 +84,6 @@ class SearchViewModel @Inject constructor( .map { Event(it) } .asLiveData() - init { - isMandatoryFingerprintRequired() - } - /** * Current search cancel token after a search request has been performed */ @@ -140,10 +142,19 @@ class SearchViewModel @Inject constructor( _state.update { it.copy(searchDepth = it.searchDepth - 1) } } + /** + * Handles Folder item clicked on [SearchFragment] + */ + fun onFolderClicked(handle: Long, lastFirstVisiblePosition: Int) { + setSearchParentHandle(handle) + increaseSearchDepth() + lastPositionStack.push(lastFirstVisiblePosition) + } + /** * Increase by 1 the search depth */ - fun increaseSearchDepth() = viewModelScope.launch { + private fun increaseSearchDepth() = viewModelScope.launch { _state.update { it.copy(searchDepth = it.searchDepth + 1) } } @@ -332,13 +343,30 @@ class SearchViewModel @Inject constructor( fun getOrder() = runBlocking { getCloudSortOrder() } /** - * Gets the feature flag value & updates state + * Handles back click [SearchFragment] */ - private fun isMandatoryFingerprintRequired() { - viewModelScope.launch { - _state.update { - it.copy(isMandatoryFingerPrintVerificationRequired = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) + fun onBackClicked() { + cancelSearch() + val levelSearch = _state.value.searchDepth + if (levelSearch >= 0) { + if (levelSearch > 0) { + viewModelScope.launch { + getSearchParentNodeHandle(_state.value.searchParentHandle)?.let { + setSearchParentHandle(it) + } ?: run { + setSearchParentHandle(-1) + } + } + } else { + setSearchParentHandle(-1) } } } + + /** + * Pop scroll position for previous depth + * + * @return last position saved + */ + fun popLastPositionStack(): Int = lastPositionStack.takeIf { it.isNotEmpty() }?.pop() ?: 0 } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 5a69738d9c8..66ab4aed975 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -250,7 +250,6 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) - adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) adapter?.setUnverifiedIncomingNodeHandles(it.unVerifiedIncomingNodeHandles) updateNodes(it.nodes) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 2d4a7343adb..5f52f9ac7b6 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -11,10 +11,8 @@ import mega.privacy.android.app.domain.usecase.AuthorizeNode import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.incoming.model.IncomingSharesState import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares @@ -36,7 +34,6 @@ class IncomingSharesViewModel @Inject constructor( private val getCloudSortOrder: GetCloudSortOrder, private val getOthersSortOrder: GetOthersSortOrder, monitorNodeUpdates: MonitorNodeUpdates, - private val getFeatureFlagValue: GetFeatureFlagValue, private val getUnverifiedIncomingShares: GetUnverifiedIncomingShares, ) : ViewModel() { @@ -71,10 +68,6 @@ class IncomingSharesViewModel @Inject constructor( } } - viewModelScope.launch { - isMandatoryFingerprintRequired() - } - viewModelScope.launch { val unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder) .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } @@ -208,13 +201,4 @@ class IncomingSharesViewModel @Inject constructor( ?.let { getNodeByHandle(it) == null } ?: true } - - /** - * Gets the feature flag value & updates state - */ - private suspend fun isMandatoryFingerprintRequired() { - _state.update { - it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) - } - } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt index 46dcfbed536..1cc0f9f0e1d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt @@ -234,7 +234,6 @@ class LinksFragment : MegaNodeBaseFragment() { adapter?.isMultipleSelect = false recyclerView?.adapter = adapter - adapter?.setMandatoryFingerprintVerificationValue(viewModel.mandatoryFingerPrintVerificationState.value) } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksViewModel.kt index fbdd3f4fef3..7aaf7a8e727 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksViewModel.kt @@ -10,10 +10,8 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetPublicLinks import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.links.model.LinksState import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetLinksSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import nz.mega.sdk.MegaApiJava @@ -33,7 +31,6 @@ class LinksViewModel @Inject constructor( private val getCloudSortOrder: GetCloudSortOrder, private val getLinksSortOrder: GetLinksSortOrder, monitorNodeUpdates: MonitorNodeUpdates, - private val getFeatureFlagValue: GetFeatureFlagValue, ) : ViewModel() { /** private UI state */ @@ -45,14 +42,6 @@ class LinksViewModel @Inject constructor( /** stack of scroll position for each depth */ private val lastPositionStack: Stack = Stack() - private val _mandatoryFingerPrintVerificationState = MutableStateFlow(false) - - /** - * State for [MandatoryFingerPrintVerification] feature flag value - */ - val mandatoryFingerPrintVerificationState: StateFlow = - _mandatoryFingerPrintVerificationState - init { viewModelScope.launch { refreshNodes()?.let { setNodes(it) } @@ -61,7 +50,6 @@ class LinksViewModel @Inject constructor( refreshNodes()?.let { setNodes(it) } } } - isMandatoryFingerprintRequired() } /** @@ -182,15 +170,4 @@ class LinksViewModel @Inject constructor( ?.let { getNodeByHandle(it) == null } ?: true } - - /** - * Gets the feature flag value & updates state - */ - private fun isMandatoryFingerprintRequired() { - viewModelScope.launch { - _mandatoryFingerPrintVerificationState.update { - getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification) - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 4810411a01a..303b83bbf2f 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -233,7 +233,6 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() setEmptyView(it.isInvalidHandle) - adapter?.setMandatoryFingerprintVerificationValue(it.isMandatoryFingerprintVerificationNeeded) adapter?.setUnverifiedOutgoingNodeHandles(it.unVerifiedOutgoingNodeHandles) updateNodes(it.nodes) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index f39e518dbb3..cd932bf01e5 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -10,10 +10,8 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.model.OutgoingSharesState import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares @@ -34,7 +32,6 @@ class OutgoingSharesViewModel @Inject constructor( private val getCloudSortOrder: GetCloudSortOrder, private val getOthersSortOrder: GetOthersSortOrder, monitorNodeUpdates: MonitorNodeUpdates, - private val getFeatureFlagValue: GetFeatureFlagValue, private val getUnverifiedOutgoingShares: GetUnverifiedOutgoingShares, ) : ViewModel() { @@ -56,10 +53,6 @@ class OutgoingSharesViewModel @Inject constructor( } } - viewModelScope.launch { - isMandatoryFingerprintRequired() - } - viewModelScope.launch { val unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder) .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } @@ -192,13 +185,4 @@ class OutgoingSharesViewModel @Inject constructor( ?.let { getNodeByHandle(it) == null } ?: true } - - /** - * Gets the feature flag value & updates state - */ - private suspend fun isMandatoryFingerprintRequired() { - _state.update { - it.copy(isMandatoryFingerprintVerificationNeeded = getFeatureFlagValue(AppFeatures.MandatoryFingerprintVerification)) - } - } } \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModelTest.kt index fa428734344..0ff7999f450 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModelTest.kt @@ -14,13 +14,11 @@ import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.R -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsViewModel import mega.privacy.android.domain.entity.contacts.AccountCredentials import mega.privacy.android.domain.exception.MegaException import mega.privacy.android.domain.usecase.AreCredentialsVerified import mega.privacy.android.domain.usecase.GetContactCredentials -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetMyCredentials import mega.privacy.android.domain.usecase.MonitorConnectivity import mega.privacy.android.domain.usecase.ResetCredentials @@ -30,7 +28,6 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.TestRule -import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.whenever @@ -102,13 +99,6 @@ class AuthenticityCredentialsViewModelTest { private val monitorConnectivity = mock { on { invoke() }.thenReturn(MutableStateFlow(true)) } - private val getFeatureFlagValue = - mock { - onBlocking { - invoke(AppFeatures.MandatoryFingerprintVerification) - }.thenReturn(true) - } - @Before fun setUp() { Dispatchers.setMain(StandardTestDispatcher(scheduler)) @@ -119,7 +109,6 @@ class AuthenticityCredentialsViewModelTest { verifyCredentials = verifyCredentials, resetCredentials = resetCredentials, monitorConnectivity = monitorConnectivity, - getFeatureFlagValue = getFeatureFlagValue, ) } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt index 955db619078..053860cf6c5 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt @@ -149,13 +149,10 @@ class AuthenticityCredentialsViewTest { initComposeRuleContent(AuthenticityCredentialsState( contactCredentials = contactCredentials, myAccountCredentials = AccountCredentials.MyAccountCredentials(myCredentials), - isMandatoryFingerPrintVerificationNeeded = true )) composeTestRule.onNodeWithTag("CONTACT_VERIFICATION_BANNER_VIEW").assertExists() composeTestRule.onNodeWithText(R.string.shared_items_verify_credentials_verify_person_banner_label) .assertExists() - composeTestRule.onNodeWithText(R.string.shared_items_verify_credentials_information) - .assertExists() composeTestRule.onNodeWithText(R.string.authenticity_credentials_label) .assertExists() } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index 2e6ce45c653..f67a2461e34 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -21,7 +21,6 @@ import mega.privacy.android.app.domain.usecase.GetPrimarySyncHandle import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.manager.ManagerViewModel import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.manager.model.TransfersTab @@ -74,14 +73,7 @@ class ManagerViewModelTest { private val checkCameraUpload = mock() private val getCloudSortOrder = mock() private val monitorConnectivity = mock() - - private val getFeatureFlagValue = - mock { - onBlocking { - invoke(AppFeatures.MandatoryFingerprintVerification) - }.thenReturn(true) - } - + private val getFeatureFlagValue = mock() private val getUnverifiedOutgoingShares = mock() private val getUnverifiedIncomingShares = mock() diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index d423fda0313..4819018e207 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -14,7 +14,6 @@ import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.AuthorizeNode import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder @@ -23,7 +22,6 @@ import mega.privacy.android.domain.entity.node.NodeChanges import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares @@ -58,13 +56,6 @@ class IncomingSharesViewModelTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() - private val getFeatureFlagValue = - mock { - onBlocking { - invoke(AppFeatures.MandatoryFingerprintVerification) - }.thenReturn(true) - } - private val getUnverifiedIncomingShares = mock { val shareData = ShareData("user", 8766L, 0, 987654678L, true) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) @@ -90,7 +81,6 @@ class IncomingSharesViewModelTest { getCloudSortOrder, getOtherSortOrder, monitorNodeUpdates, - getFeatureFlagValue, getUnverifiedIncomingShares, ) } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/links/LinksViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/links/LinksViewModelTest.kt index 98f949388cc..b651cf73f27 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/links/LinksViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/links/LinksViewModelTest.kt @@ -12,14 +12,12 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetPublicLinks -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.links.LinksViewModel import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetLinksSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import nz.mega.sdk.MegaNode @@ -51,13 +49,6 @@ class LinksViewModelTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() - private val getFeatureFlagValue = - mock { - onBlocking { - invoke(AppFeatures.MandatoryFingerprintVerification) - }.thenReturn(true) - } - @Before fun setUp() { Dispatchers.setMain(UnconfinedTestDispatcher()) @@ -68,7 +59,6 @@ class LinksViewModelTest { getCloudSortOrder, getLinksSortOrder, monitorNodeUpdates, - getFeatureFlagValue, ) } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index a97d38b03e3..d9f187e04eb 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -12,7 +12,6 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode -import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder @@ -20,7 +19,6 @@ import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.usecase.GetCloudSortOrder -import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares @@ -53,13 +51,6 @@ class OutgoingSharesViewModelTest { @get:Rule var instantExecutorRule = InstantTaskExecutorRule() - private val getFeatureFlagValue = - mock { - onBlocking { - invoke(AppFeatures.MandatoryFingerprintVerification) - }.thenReturn(true) - } - private val getUnverifiedOutgoingShares = mock { val shareData = ShareData("user", 8766L, 0, 987654678L, true) onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) @@ -79,7 +70,6 @@ class OutgoingSharesViewModelTest { getCloudSortOrder, getOtherSortOrder, monitorNodeUpdates, - getFeatureFlagValue, getUnverifiedOutgoingShares, ) } From e27c99a8c545aa5ce89388af10d41fc644f0c5fd Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Fri, 17 Feb 2023 21:36:43 +1300 Subject: [PATCH 209/334] Unit test case failure fixed --- .../SecurityUpgradeDialogTest.kt | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt diff --git a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt new file mode 100644 index 00000000000..1dbe892bf93 --- /dev/null +++ b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt @@ -0,0 +1,46 @@ +package test.mega.privacy.android.app.presentation.fingerprintauth + +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import dagger.hilt.android.testing.HiltAndroidTest +import mega.privacy.android.app.R +import mega.privacy.android.app.presentation.fingerprintauth.SecurityUpgradeDialogView +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import test.mega.privacy.android.app.onNodeWithText + +@HiltAndroidTest +@RunWith(AndroidJUnit4::class) +class SecurityUpgradeDialogTest { + + @get:Rule + val composeTestRule = createComposeRule() + + private fun initComposeRule() { + composeTestRule.setContent { + SecurityUpgradeDialogView(folderNames = listOf("folder name 1 ", + "folder name 2 ", + "folder name 3 "), + onOkClick = { }, + onCancelClick = {}) + } + } + + @Test + fun test_that_imageview_resource_is_as_expected() { + initComposeRule() + composeTestRule.run { + onNodeWithTag("HeaderImage").assertIsDisplayed() + onNodeWithText(R.string.shared_items_security_upgrade_dialog_title).assertIsDisplayed() + onNodeWithText(R.string.shared_items_security_upgrade_dialog_content).assertIsDisplayed() + onNodeWithTag("SharedNodeInfo").assertIsDisplayed() + onNodeWithText(R.string.general_ok).assertIsDisplayed() + onNodeWithText(R.string.button_cancel).assertIsDisplayed() + } + } +} \ No newline at end of file From 516fb8d34987926896babb971788eb78671813a2 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 15 Feb 2023 20:13:31 +1300 Subject: [PATCH 210/334] AND-15405 NodeOptionsBottomSheetViewModel scoped to fragment --- .../NodeOptionsBottomSheetDialogFragment.java | 13 ++++++++----- .../modalbottomsheet/NodeOptionsBottomSheetState.kt | 2 +- .../NodeOptionsBottomSheetViewModel.kt | 11 +++++++++++ .../NodeOptionsBottomSheetViewModelTest.kt | 6 +++--- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 23fcb7d7e88..b5fd50272c1 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -193,7 +193,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class); incomingSharesViewModel = new ViewModelProvider(requireActivity()).get(IncomingSharesViewModel.class); outgoingSharesViewModel = new ViewModelProvider(requireActivity()).get(OutgoingSharesViewModel.class); - nodeOptionsBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(NodeOptionsBottomSheetViewModel.class); + nodeOptionsBottomSheetViewModel = new ViewModelProvider(this).get(NodeOptionsBottomSheetViewModel.class); } @Nullable @@ -742,11 +742,14 @@ && isNodeUnverified(state.getUnVerifiedOutgoingNodeHandles())) { } ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), nodeOptionsBottomSheetViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (state.isOpenShareDialogSuccess()) { - showShareFolderOptions(); - } else { - showSnackbar(requireActivity(), requireActivity().getString(R.string.general_something_went_wrong_error)); + if(state.isOpenShareDialogSuccess() != null) { + if (state.isOpenShareDialogSuccess()) { + showShareFolderOptions(); + } else { + showSnackbar(requireActivity(), getString(R.string.general_something_went_wrong_error)); + } } + nodeOptionsBottomSheetViewModel.resetIsOpenShareDialogSuccess(); return Unit.INSTANCE; }); } diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt index b9873472936..2875eee872d 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetState.kt @@ -8,5 +8,5 @@ package mega.privacy.android.app.modalbottomsheet */ data class NodeOptionsBottomSheetState( val currentNodeHandle: Long = -1L, - val isOpenShareDialogSuccess: Boolean = false, + val isOpenShareDialogSuccess: Boolean? = null, ) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt index cf70e2ad2ac..3b483b693d8 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt @@ -69,4 +69,15 @@ class NodeOptionsBottomSheetViewModel @Inject constructor( } } } + + /** + * Change the value of isOpenShareDialogSuccess to false after it is consumed. + */ + fun resetIsOpenShareDialogSuccess() { + viewModelScope.launch { + _state.update { + it.copy(isOpenShareDialogSuccess = null) + } + } + } } \ No newline at end of file diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt index 8b715e1556d..02153c8769a 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/NodeOptionsBottomSheetViewModelTest.kt @@ -34,7 +34,7 @@ class NodeOptionsBottomSheetViewModelTest { underTest.state.test { val initial = awaitItem() Truth.assertThat(initial.currentNodeHandle).isEqualTo(-1L) - Truth.assertThat(initial.isOpenShareDialogSuccess).isEqualTo(false) + Truth.assertThat(initial.isOpenShareDialogSuccess).isEqualTo(null) } } @@ -43,7 +43,7 @@ class NodeOptionsBottomSheetViewModelTest { underTest.callOpenShareDialog(3829183L) underTest.state.runCatching { this.test { - assertTrue(awaitItem().isOpenShareDialogSuccess) + awaitItem().isOpenShareDialogSuccess?.let { assertTrue(it) } } } } @@ -53,7 +53,7 @@ class NodeOptionsBottomSheetViewModelTest { underTest.callOpenShareDialog(-1) underTest.state.runCatching { this.test { - assertFalse(awaitItem().isOpenShareDialogSuccess) + awaitItem().isOpenShareDialogSuccess?.let { assertFalse(it) } } } } From 7117836d4e0ce00b9b6f69801fb6f9de3128109c Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 20 Feb 2023 14:45:49 +0530 Subject: [PATCH 211/334] Update Prebuilt ask version , Remove unnecessary changes --- .../privacy/android/app/di/GetNodeModule.kt | 15 +------ .../android/app/main/ManagerActivity.java | 42 +------------------ .../manager/model/ManagerState.kt | 3 +- .../presentation/search/SearchViewModel.kt | 2 +- .../shares/incoming/IncomingSharesFragment.kt | 1 - build.gradle | 2 +- 6 files changed, 6 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt index 5eef302b045..afb6fd4c70a 100644 --- a/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt +++ b/app/src/main/java/mega/privacy/android/app/di/GetNodeModule.kt @@ -11,16 +11,15 @@ import mega.privacy.android.app.domain.usecase.CopyNode import mega.privacy.android.app.domain.usecase.GetChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.OpenShareDialog -import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.app.namecollision.usecase.CheckNameCollisionUseCase import mega.privacy.android.app.usecase.MoveNodeUseCase +import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares -import mega.privacy.android.domain.usecase.SetSecureFlag +import mega.privacy.android.domain.usecase.UpgradeSecurity import mega.privacy.android.domain.usecase.filenode.CopyNodeByHandle import mega.privacy.android.domain.usecase.filenode.CopyNodeByHandleChangingName import mega.privacy.android.domain.usecase.filenode.MoveNodeByHandle -import mega.privacy.android.domain.usecase.UpgradeSecurity /** * Get node module @@ -132,16 +131,6 @@ abstract class GetNodeModule { moveNodeUseCase.move(nodeToCopy.longValue, newNodeParent.longValue).await() } - /** - * Provides [SetSecureFlag] implementation - * - * @param megaNodeRepository [MegaNodeRepository] - * @return [SetSecureFlag] - */ - @Provides - fun provideSetSecureFlag(megaNodeRepository: MegaNodeRepository): SetSecureFlag = - SetSecureFlag(megaNodeRepository::setSecureFlag) - /** * Provides [OpenShareDialog] implementation * diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 1a84086c6cb..148fca341fd 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -16,15 +16,12 @@ import static mega.privacy.android.app.constants.BroadcastConstants.INVALID_ACTION; import static mega.privacy.android.app.constants.EventConstants.EVENT_CALL_ON_HOLD_CHANGE; import static mega.privacy.android.app.constants.EventConstants.EVENT_CALL_STATUS_CHANGE; -import static mega.privacy.android.app.constants.EventConstants.EVENT_FAILED_TRANSFERS; import static mega.privacy.android.app.constants.EventConstants.EVENT_FINISH_ACTIVITY; import static mega.privacy.android.app.constants.EventConstants.EVENT_REFRESH; import static mega.privacy.android.app.constants.EventConstants.EVENT_REFRESH_PHONE_NUMBER; import static mega.privacy.android.app.constants.EventConstants.EVENT_SESSION_ON_HOLD_CHANGE; -import static mega.privacy.android.app.constants.EventConstants.EVENT_TRANSFER_OVER_QUOTA; import static mega.privacy.android.app.constants.EventConstants.EVENT_UPDATE_VIEW_MODE; import static mega.privacy.android.app.constants.EventConstants.EVENT_USER_EMAIL_UPDATED; -import static mega.privacy.android.app.constants.EventConstants.EVENT_USER_NAME_UPDATED; import static mega.privacy.android.app.constants.IntentConstants.ACTION_OPEN_ACHIEVEMENTS; import static mega.privacy.android.app.constants.IntentConstants.EXTRA_ACCOUNT_TYPE; import static mega.privacy.android.app.constants.IntentConstants.EXTRA_ASK_PERMISSIONS; @@ -96,7 +93,6 @@ import static mega.privacy.android.app.utils.JobUtil.fireCameraUploadJob; import static mega.privacy.android.app.utils.JobUtil.fireCancelCameraUploadJob; import static mega.privacy.android.app.utils.JobUtil.fireStopCameraUploadJob; -import static mega.privacy.android.app.utils.JobUtil.stopCameraUploadSyncHeartbeatWorkers; import static mega.privacy.android.app.utils.MegaApiUtils.calculateDeepBrowserTreeIncoming; import static mega.privacy.android.app.utils.MegaNodeDialogUtil.ACTION_BACKUP_FAB; import static mega.privacy.android.app.utils.MegaNodeDialogUtil.ACTION_BACKUP_SHARE_FOLDER; @@ -120,7 +116,6 @@ import static mega.privacy.android.app.utils.OfflineUtils.saveOffline; import static mega.privacy.android.app.utils.StringResourcesUtils.getQuantityString; import static mega.privacy.android.app.utils.TextUtil.isTextEmpty; -import static mega.privacy.android.app.utils.TimeUtils.getHumanizedTime; import static mega.privacy.android.app.utils.UploadUtil.chooseFiles; import static mega.privacy.android.app.utils.UploadUtil.chooseFolder; import static mega.privacy.android.app.utils.UploadUtil.getFolder; @@ -218,7 +213,6 @@ import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SearchView; import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.app.ActivityCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; @@ -298,7 +292,6 @@ import mega.privacy.android.app.fragments.homepage.documents.DocumentsFragment; import mega.privacy.android.app.fragments.homepage.main.HomepageFragment; import mega.privacy.android.app.fragments.homepage.main.HomepageFragmentDirections; -import mega.privacy.android.app.fragments.managerFragments.cu.CustomHideBottomViewOnScrollBehaviour; import mega.privacy.android.app.fragments.offline.OfflineFragment; import mega.privacy.android.app.fragments.recent.RecentsBucketFragment; import mega.privacy.android.app.fragments.settingsFragments.cookie.CookieDialogHandler; @@ -324,7 +317,6 @@ import mega.privacy.android.app.main.listeners.CreateGroupChatWithPublicLink; import mega.privacy.android.app.main.listeners.FabButtonListener; import mega.privacy.android.app.main.managerSections.CompletedTransfersFragment; -import mega.privacy.android.app.main.managerSections.NotificationsFragment; import mega.privacy.android.app.main.managerSections.TransfersFragment; import mega.privacy.android.app.main.managerSections.TurnOnNotificationsFragment; import mega.privacy.android.app.main.megachat.BadgeDrawerArrowDrawable; @@ -355,12 +347,9 @@ import mega.privacy.android.app.objects.PasscodeManagement; import mega.privacy.android.app.presentation.clouddrive.FileBrowserFragment; import mega.privacy.android.app.presentation.clouddrive.FileBrowserViewModel; -import mega.privacy.android.app.presentation.fileinfo.FileInfoActivity; import mega.privacy.android.app.presentation.fingerprintauth.SecurityUpgradeDialogFragment; -import mega.privacy.android.app.presentation.folderlink.FolderLinkActivity; import mega.privacy.android.app.presentation.inbox.InboxFragment; import mega.privacy.android.app.presentation.inbox.InboxViewModel; -import mega.privacy.android.app.presentation.login.LoginActivity; import mega.privacy.android.app.presentation.manager.ManagerViewModel; import mega.privacy.android.app.presentation.manager.UnreadUserAlertsCheckType; import mega.privacy.android.app.presentation.manager.UserInfoViewModel; @@ -374,7 +363,6 @@ import mega.privacy.android.app.presentation.photos.albums.AlbumDynamicContentFragment; import mega.privacy.android.app.presentation.photos.mediadiscovery.MediaDiscoveryFragment; import mega.privacy.android.app.presentation.photos.timeline.photosfilter.PhotosFilterFragment; -import mega.privacy.android.app.presentation.qrcode.scan.ScanCodeFragment; import mega.privacy.android.app.presentation.rubbishbin.RubbishBinFragment; import mega.privacy.android.app.presentation.rubbishbin.RubbishBinViewModel; import mega.privacy.android.app.presentation.search.SearchFragment; @@ -399,7 +387,6 @@ import mega.privacy.android.app.upgradeAccount.UpgradeAccountActivity; import mega.privacy.android.app.usecase.CopyNodeUseCase; import mega.privacy.android.app.usecase.DownloadNodeUseCase; -import mega.privacy.android.app.usecase.GetNodeUseCase; import mega.privacy.android.app.usecase.MoveNodeUseCase; import mega.privacy.android.app.usecase.RemoveNodeUseCase; import mega.privacy.android.app.usecase.UploadUseCase; @@ -439,7 +426,6 @@ import mega.privacy.android.domain.entity.contacts.ContactRequest; import mega.privacy.android.domain.entity.contacts.ContactRequestStatus; import mega.privacy.android.domain.entity.preference.ViewType; -import mega.privacy.android.domain.entity.user.UserCredentials; import mega.privacy.android.domain.qualifier.ApplicationScope; import nz.mega.documentscanner.DocumentScannerActivity; import nz.mega.sdk.MegaAccountDetails; @@ -545,8 +531,6 @@ public class ManagerActivity extends TransfersManagementActivity @Inject RemoveNodeUseCase removeNodeUseCase; @Inject - GetNodeUseCase getNodeUseCase; - @Inject GetChatChangesUseCase getChatChangesUseCase; @Inject DownloadNodeUseCase downloadNodeUseCase; @@ -1449,12 +1433,6 @@ protected void onCreate(Bundle savedInstanceState) { LiveEventBus.get(EVENT_REFRESH_PHONE_NUMBER, Boolean.class) .observeForever(refreshAddPhoneNumberButtonObserver); - LiveEventBus.get(EVENT_FAILED_TRANSFERS, Boolean.class).observe(this, failed -> { - if (drawerItem == DrawerItem.TRANSFERS && getTabItemTransfers() == TransfersTab.COMPLETED_TAB) { - retryTransfers.setVisible(failed); - } - }); - registerReceiver(transferFinishReceiver, new IntentFilter(BROADCAST_ACTION_TRANSFER_FINISH)); LiveEventBus.get(EVENT_CALL_STATUS_CHANGE, MegaChatCall.class).observe(this, callStatusObserver); @@ -1534,28 +1512,18 @@ protected void onCreate(Bundle savedInstanceState) { prefs = dbH.getPreferences(); if (prefs == null) { firstTimeAfterInstallation = true; - isList = true; } else { if (prefs.getFirstTime() == null) { firstTimeAfterInstallation = true; } else { firstTimeAfterInstallation = Boolean.parseBoolean(prefs.getFirstTime()); } - if (prefs.getPreferredViewList() == null) { - isList = true; - } else { - isList = Boolean.parseBoolean(prefs.getPreferredViewList()); - } } if (firstTimeAfterInstallation) { setStartScreenTimeStamp(this); } - Timber.d("Preferred View List: %s", isList); - - LiveEventBus.get(EVENT_LIST_GRID_CHANGE, Boolean.class).post(isList); - handler = new Handler(); Timber.d("Set view"); @@ -2479,7 +2447,7 @@ public void onPageScrollStateChanged(int state) { } else { Timber.d("Backup warning dialog is not show"); } - } + } /** * collecting Flows from ViewModels @@ -9867,14 +9835,6 @@ public boolean isList() { return isList; } - public void setList(boolean isList) { - this.isList = isList; - } - - public boolean isListCameraUploads() { - return false; - } - public boolean getFirstLogin() { return viewModel.getState().getValue().isFirstLogin(); } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt index ed40db8756e..8311d327d9c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/model/ManagerState.kt @@ -3,7 +3,6 @@ package mega.privacy.android.app.presentation.manager.model /** * Manager UI state * - * @param rubbishBinParentHandle current rubbish bin parent handle * @param isFirstNavigationLevel true if the navigation level is the first level * @param sharesTab current tab in shares screen * @param transfersTab current tab in transfers screen @@ -17,7 +16,6 @@ package mega.privacy.android.app.presentation.manager.model * @param showSyncSection Boolean to show sync section */ data class ManagerState( - val rubbishBinParentHandle: Long = -1L, val isFirstNavigationLevel: Boolean = true, val sharesTab: SharesTab = SharesTab.INCOMING_TAB, val transfersTab: TransfersTab = TransfersTab.NONE, @@ -28,4 +26,5 @@ data class ManagerState( val nodeUpdateReceived: Boolean = false, val pendingActionsCount: Int = 0, val shouldAlertUserAboutSecurityUpgrade: Boolean = false, + val showSyncSection: Boolean = false ) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt index 52e378a5609..22581ec3e33 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt @@ -154,7 +154,7 @@ class SearchViewModel @Inject constructor( /** * Increase by 1 the search depth */ - private fun increaseSearchDepth() = viewModelScope.launch { + fun increaseSearchDepth() = viewModelScope.launch { _state.update { it.copy(searchDepth = it.searchDepth + 1) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 66ab4aed975..28a3af32822 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -68,7 +68,6 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { else getGridView(inflater, container) initAdapter() - observe() selectNewlyAddedNodes() return view diff --git a/build.gradle b/build.gradle index 0d793937bd3..67dc91dc837 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230209.083412-rel' + megaSdkVersion = '20230216.073343-dev' // App dependencies accompanistLayoutVersion = '0.24.13-rc' From bfe96c59e84cbca32272dee6b5e19d9aa10987ac Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 20 Feb 2023 16:26:02 +0530 Subject: [PATCH 212/334] Remove un-necessary changes --- .../presentation/search/SearchViewModel.kt | 1 - .../manager/ManagerViewModelTest.kt | 48 ------------------- .../SecurityUpgradeDialogTest.kt | 2 - .../android/data/facade/MegaChatApiFacade.kt | 12 +++-- .../data/model/ScheduledMeetingUpdate.kt | 6 ++- .../data/repository/MegaNodeRepository.kt | 7 +++ .../data/repository/MegaNodeRepositoryImpl.kt | 43 +++++++++++++++-- 7 files changed, 59 insertions(+), 60 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt index 22581ec3e33..66e685b4f8c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchViewModel.kt @@ -22,7 +22,6 @@ import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.search.model.SearchState import mega.privacy.android.app.search.usecase.SearchNodesUseCase import mega.privacy.android.app.search.usecase.SearchNodesUseCase.Companion.TYPE_GENERAL -import mega.privacy.android.data.mapper.SortOrderIntMapper import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.RootNodeExists diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index f67a2461e34..15e132fd9ea 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -18,7 +18,6 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetInboxNode import mega.privacy.android.app.domain.usecase.GetPrimarySyncHandle -import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetSecondarySyncHandle import mega.privacy.android.app.domain.usecase.MonitorGlobalUpdates import mega.privacy.android.app.presentation.manager.ManagerViewModel @@ -58,7 +57,6 @@ class ManagerViewModelTest { private val monitorGlobalUpdates = mock() private val monitorNodeUpdates = FakeMonitorUpdates() - private val getRubbishBinNodeByHandle = mock() private val getNumUnreadUserAlerts = mock() private val hasInboxChildren = mock() private val monitorContactRequestUpdates = mock() @@ -93,7 +91,6 @@ class ManagerViewModelTest { underTest = ManagerViewModel( monitorNodeUpdates = monitorNodeUpdates, monitorGlobalUpdates = monitorGlobalUpdates, - getRubbishBinChildrenNode = getRubbishBinNodeByHandle, monitorContactRequestUpdates = monitorContactRequestUpdates, getNumUnreadUserAlerts = getNumUnreadUserAlerts, hasInboxChildren = hasInboxChildren, @@ -142,7 +139,6 @@ class ManagerViewModelTest { setUnderTest() underTest.state.test { val initial = awaitItem() - assertThat(initial.rubbishBinParentHandle).isEqualTo(-1L) assertThat(initial.isFirstNavigationLevel).isTrue() assertThat(initial.sharesTab).isEqualTo(SharesTab.INCOMING_TAB) assertThat(initial.transfersTab).isEqualTo(TransfersTab.NONE) @@ -153,19 +149,6 @@ class ManagerViewModelTest { } } - @Test - fun `test that rubbish bin parent handle is updated if new value provided`() = runTest { - setUnderTest() - - underTest.state.map { it.rubbishBinParentHandle }.distinctUntilChanged() - .test { - val newValue = 123456789L - assertThat(awaitItem()).isEqualTo(-1L) - underTest.setRubbishBinParentHandle(newValue) - assertThat(awaitItem()).isEqualTo(newValue) - } - } - @Test fun `test that is first navigation level is updated if new value provided`() = runTest { setUnderTest() @@ -229,37 +212,6 @@ class ManagerViewModelTest { underTest.updateContactsRequests.test().assertNoValue() } - @Test - fun `test that rubbish bin node updates live data is set when node updates triggered from use case`() = - runTest { - whenever(getRubbishBinNodeByHandle(any())).thenReturn(listOf(mock(), mock())) - - setUnderTest() - - runCatching { - val result = - underTest.updateRubbishBinNodes.test().awaitValue(50, TimeUnit.MILLISECONDS) - monitorNodeUpdates.emit(listOf(mock())) - result - }.onSuccess { result -> - result.assertValue { it.getContentIfNotHandled()?.size == 2 } - } - } - - @Test - fun `test that rubbish bin node updates live data is not set when get rubbish bin node returns a null list`() = - runTest { - whenever(getRubbishBinNodeByHandle(any())).thenReturn(null) - - setUnderTest() - - runCatching { - underTest.updateRubbishBinNodes.test().awaitValue(50, TimeUnit.MILLISECONDS) - }.onSuccess { result -> - result.assertNoValue() - } - } - @Test fun `test that user updates live data is set when user updates triggered from use case`() = runTest { diff --git a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt index 1dbe892bf93..73a51faf9b6 100644 --- a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt +++ b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogTest.kt @@ -2,9 +2,7 @@ package test.mega.privacy.android.app.presentation.fingerprintauth import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.junit4.createComposeRule -import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.test.onNodeWithText import androidx.test.ext.junit.runners.AndroidJUnit4 import dagger.hilt.android.testing.HiltAndroidTest import mega.privacy.android.app.R diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaChatApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaChatApiFacade.kt index 238a52e43e6..7adaaf2c3bd 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaChatApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaChatApiFacade.kt @@ -280,13 +280,19 @@ internal class MegaChatApiFacade @Inject constructor( trySend(ScheduledMeetingUpdate.OnChatSchedMeetingUpdate(scheduledMeeting)) } - override fun onSchedMeetingOccurrencesUpdate(api: MegaChatApiJava?, chatId: Long) { - trySend(ScheduledMeetingUpdate.OnSchedMeetingOccurrencesUpdate(chatId)) + override fun onSchedMeetingOccurrencesUpdate( + api: MegaChatApiJava?, + chatId: Long, + append: Boolean, + ) { + trySend(ScheduledMeetingUpdate.OnSchedMeetingOccurrencesUpdate(chatId, append)) } } chatApi.addSchedMeetingListener(listener) - awaitClose { chatApi.removeSchedMeetingListener(listener) } + awaitClose { + chatApi.removeSchedMeetingListener(listener) + } }.shareIn(sharingScope, SharingStarted.WhileSubscribed()) override fun getAllScheduledMeetings(): List? = diff --git a/data/src/main/java/mega/privacy/android/data/model/ScheduledMeetingUpdate.kt b/data/src/main/java/mega/privacy/android/data/model/ScheduledMeetingUpdate.kt index 4b61469c785..11a03a04171 100644 --- a/data/src/main/java/mega/privacy/android/data/model/ScheduledMeetingUpdate.kt +++ b/data/src/main/java/mega/privacy/android/data/model/ScheduledMeetingUpdate.kt @@ -20,7 +20,9 @@ sealed class ScheduledMeetingUpdate { /** * On chat scheduled meeting occurrences item update. * - * @property chatId + * @property chatId Chat id + * @property append If append is true, new occurrences has been received from API (no need to discard current ones) */ - data class OnSchedMeetingOccurrencesUpdate(val chatId: Long) : ScheduledMeetingUpdate() + data class OnSchedMeetingOccurrencesUpdate(val chatId: Long, val append: Boolean) : + ScheduledMeetingUpdate() } diff --git a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt index 06e25782ead..cedb75f6c51 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepository.kt @@ -259,4 +259,11 @@ interface MegaNodeRepository { * Update cryptographic security */ suspend fun upgradeSecurity() + + /** + * Sets the secure share flag to true or false + * + * @param enable : Boolean + */ + suspend fun setSecureFlag(enable: Boolean) } \ No newline at end of file diff --git a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepositoryImpl.kt b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepositoryImpl.kt index 663e326946b..26e28937e3f 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepositoryImpl.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/MegaNodeRepositoryImpl.kt @@ -3,8 +3,10 @@ package mega.privacy.android.data.repository import android.content.Context import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import mega.privacy.android.data.extensions.failWithError +import mega.privacy.android.data.extensions.getRequestListener import mega.privacy.android.data.gateway.CacheFolderGateway import mega.privacy.android.data.gateway.FileGateway import mega.privacy.android.data.gateway.MegaLocalStorageGateway @@ -21,6 +23,7 @@ import mega.privacy.android.data.mapper.NodeMapper import mega.privacy.android.data.mapper.OfflineNodeInformationMapper import mega.privacy.android.data.mapper.SortOrderIntMapper import mega.privacy.android.domain.entity.FolderVersionInfo +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.exception.MegaException @@ -255,10 +258,42 @@ internal class MegaNodeRepositoryImpl @Inject constructor( megaExceptionMapper(megaApiGateway.checkAccessErrorExtended(node, level)) } + override suspend fun getUnverifiedIncomingShares(order: SortOrder): List = + withContext(ioDispatcher) { + megaApiGateway.getUnverifiedIncomingShares(sortOrderIntMapper(order)).map { + megaShareMapper(it) + } + } + + override suspend fun getUnverifiedOutgoingShares(order: SortOrder): List = + withContext(ioDispatcher) { + megaApiGateway.getUnverifiedOutgoingShares(sortOrderIntMapper(order)).map { + megaShareMapper(it) + } + } + - override suspend fun getUnVerifiedInComingShares(): Int = 3 - //// TODO Please keep this hardcoded for now. Full functionality will be added after SDK changes are available + override suspend fun openShareDialog(megaNode: MegaNode) = withContext(ioDispatcher) { + suspendCancellableCoroutine { continuation -> + val listener = continuation.getRequestListener { return@getRequestListener } + megaApiGateway.openShareDialog(megaNode, listener) + continuation.invokeOnCancellation { + megaApiGateway.removeRequestListener(listener) + } + } + } + + override suspend fun upgradeSecurity() = withContext(ioDispatcher) { + suspendCancellableCoroutine { continuation -> + val listener = continuation.getRequestListener { return@getRequestListener } + megaApiGateway.upgradeSecurity(listener) + continuation.invokeOnCancellation { + megaApiGateway.removeRequestListener(listener) + } + } + } - override suspend fun getUnverifiedOutgoingShares(): Int = 5 - //// TODO Please keep this hardcoded for now. Full functionality will be added after SDK changes are available + override suspend fun setSecureFlag(enable: Boolean) = withContext(ioDispatcher) { + megaApiGateway.setSecureFlag(enable) + } } From f4ae4a743fbd94f11e5913fa0d9943108147b082 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 20 Feb 2023 19:34:03 +0530 Subject: [PATCH 213/334] Pre-built version updated --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 67dc91dc837..dac68ba6b5c 100644 --- a/build.gradle +++ b/build.gradle @@ -68,7 +68,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230216.073343-dev' + megaSdkVersion = '20230220.134000-rel' // App dependencies accompanistLayoutVersion = '0.24.13-rc' From 48f23792d57e2794d8f4b255760a3b55e1cea250 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Mon, 20 Feb 2023 19:50:15 +0530 Subject: [PATCH 214/334] appVersion changed from 7.4 to 7.5 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index dac68ba6b5c..0cb83733a4e 100644 --- a/build.gradle +++ b/build.gradle @@ -59,7 +59,7 @@ task clean(type: Delete) { // Define versions in a single place ext { // App - appVersion = "7.4" + appVersion = "7.5" // Sdk and tools compileSdkVersion = 33 From 29edfd967494f744192198bc650669e118be3aa1 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Tue, 21 Feb 2023 22:41:24 +1300 Subject: [PATCH 215/334] AND-15731 Fix NPE by calling updateGlobalEvents after initialisation --- .../android/app/main/ManagerActivity.java | 1 + .../presentation/manager/ManagerViewModel.kt | 25 +++++++++++-------- .../manager/ManagerViewModelTest.kt | 1 + 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 148fca341fd..f28d2a63b9c 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -1291,6 +1291,7 @@ protected void onCreate(Bundle savedInstanceState) { rubbishBinViewModel = new ViewModelProvider(this).get(RubbishBinViewModel.class); searchViewModel = new ViewModelProvider(this).get(SearchViewModel.class); userInfoViewModel = new ViewModelProvider(this).get(UserInfoViewModel.class); + viewModel.monitorGlobalEventUpgradeForUpgradeSecurity(); viewModel.getUpdateUsers().observe(this, new EventObserver<>(users -> { updateUsers(users); diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index 055e151e556..bc957b922ad 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -184,16 +184,6 @@ class ManagerViewModel @Inject constructor( it.copy(pendingActionsCount = _state.value.pendingActionsCount + outgoingShares) } } - - viewModelScope.launch { - updateGlobalEvents.collect { megaEvent -> - if (megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { - _state.update { - it.copy(shouldAlertUserAboutSecurityUpgrade = true) - } - } - } - } } /** @@ -452,4 +442,19 @@ class ManagerViewModel @Inject constructor( * Active subscription in local cache */ val activeSubscription: MegaPurchase? get() = getActiveSubscription() + + /** + * Check global events updates for [MegaEvent.EVENT_UPGRADE_SECURITY] + */ + fun monitorGlobalEventUpgradeForUpgradeSecurity() { + viewModelScope.launch { + updateGlobalEvents.collect { megaEvent -> + if (megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { + _state.update { + it.copy(shouldAlertUserAboutSecurityUpgrade = true) + } + } + } + } + } } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index 15e132fd9ea..fb515a8d403 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -146,6 +146,7 @@ class ManagerViewModelTest { assertThat(initial.shouldSendCameraBroadcastEvent).isFalse() assertThat(initial.shouldStopCameraUpload).isFalse() assertThat(initial.nodeUpdateReceived).isFalse() + assertThat(initial.shouldAlertUserAboutSecurityUpgrade).isFalse() } } From 8b168777dbabafbd026878a8a42381457a00c607 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Wed, 22 Feb 2023 08:50:04 +1300 Subject: [PATCH 216/334] AND-15735 - Display contact verification banner on contact verification screen if node is incoming --- .../android/app/main/ManagerActivity.java | 25 +++++++++++++------ .../NodeOptionsBottomSheetDialogFragment.java | 7 +++--- .../AuthenticityCredentialsActivity.kt | 4 +++ .../AuthenticityCredentialsViewModel.kt | 11 ++++++++ .../model/AuthenticityCredentialsState.kt | 2 ++ .../view/AuthenticityCredentialsView.kt | 2 +- .../SecurityUpgradeDialogFragment.kt | 4 +-- .../SecurityUpgradeDialogView.kt | 2 +- .../recentactions/RecentActionsFragment.kt | 18 +++++++++---- .../shares/incoming/IncomingSharesFragment.kt | 6 +++++ .../incoming/model/IncomingSharesState.kt | 2 -- .../outgoing/model/OutgoingSharesState.kt | 2 -- .../privacy/android/app/utils/Constants.java | 1 + .../AuthenticityCredentialsViewTest.kt | 3 ++- 14 files changed, 63 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index f28d2a63b9c..210a8230d02 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -848,6 +848,8 @@ public void onChanged(Boolean aBoolean) { public MegaNode viewInFolderNode; + private ArrayList outgoingFolderNames = new ArrayList<>(); + /** * Broadcast to update the completed transfers tab. */ @@ -2460,9 +2462,20 @@ private void collectFlows() { }); ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { - if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { - replaceFragment(SecurityUpgradeDialogFragment.Companion.newInstance(), SecurityUpgradeDialogFragment.TAG); + ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { + outgoingFolderNames.clear(); + for (int i = 0; i < outgoingSharesState.getNodes().size(); i++) { + outgoingFolderNames.add(outgoingSharesState.getNodes().get(i).getName()); + } + SecurityUpgradeDialogFragment dialog = SecurityUpgradeDialogFragment.Companion.newInstance(); + Bundle bundle = new Bundle(); + bundle.putStringArrayList("nodeNames", outgoingFolderNames); + dialog.setArguments(bundle); + dialog.show(getSupportFragmentManager(), SecurityUpgradeDialogFragment.TAG); + + return Unit.INSTANCE; + }); } updateInboxSectionVisibility(managerState.getHasInboxChildren()); @@ -2512,16 +2525,12 @@ private void collectFlows() { }); ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { - if (incomingSharesState.isMandatoryFingerprintVerificationNeeded()) { - addUnverifiedIncomingCountBadge(incomingSharesState.getUnverifiedIncomingShares().size()); - } + addUnverifiedIncomingCountBadge(incomingSharesState.getUnverifiedIncomingShares().size()); return Unit.INSTANCE; }); ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { - if (outgoingSharesState.isMandatoryFingerprintVerificationNeeded()) { - addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnverifiedOutgoingShares().size()); - } + addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnverifiedOutgoingShares().size()); return Unit.INSTANCE; }); } diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index b5fd50272c1..de580d2e2d0 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -721,8 +721,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat super.onViewCreated(view, savedInstanceState); if(nC.nodeComesFromIncoming(node)) { ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (incomingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() - && mMode == SHARED_ITEMS_MODE + if (mMode == SHARED_ITEMS_MODE && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedIncomingShares()); hideNodeActions(); @@ -731,8 +730,7 @@ && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { }); } else { ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (outgoingSharesViewModel.getState().getValue().isMandatoryFingerprintVerificationNeeded() - && mMode == SHARED_ITEMS_MODE + if (mMode == SHARED_ITEMS_MODE && isNodeUnverified(state.getUnVerifiedOutgoingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedOutgoingShares()); hideNodeActions(); @@ -1103,6 +1101,7 @@ public void onClick(View v) { break; case R.id.verify_user_option: Intent authenticityCredentialsIntent = new Intent(getActivity(), AuthenticityCredentialsActivity.class); + authenticityCredentialsIntent.putExtra(Constants.IS_NODE_INCOMING, nC.nodeComesFromIncoming(node)); authenticityCredentialsIntent.putExtra(Constants.EMAIL, user.getEmail()); requireActivity().startActivity(authenticityCredentialsIntent); break; diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsActivity.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsActivity.kt index 0016711ed03..7bf62a65878 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsActivity.kt @@ -41,6 +41,10 @@ class AuthenticityCredentialsActivity : ComponentActivity() { viewModel.requestData(it) } ?: finish() + viewModel.setShowContactVerificationBanner( + intent.extras?.getBoolean(Constants.IS_NODE_INCOMING) ?: false + ) + setContent { AuthenticityCredentialsView() } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt index 10afc13c18f..150cfc91e1d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewModel.kt @@ -143,4 +143,15 @@ class AuthenticityCredentialsViewModel @Inject constructor( * Updates state after shown error. */ fun errorShown() = _state.update { it.copy(error = null) } + + /** + * Function to update state with the boolean value to show contact verification banner + */ + fun setShowContactVerificationBanner(isNodeIncoming: Boolean = false) { + viewModelScope.launch { + _state.update { + it.copy(showContactVerificationBanner = isNodeIncoming) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt index 86fcca4430a..e5dc080ce56 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/model/AuthenticityCredentialsState.kt @@ -10,6 +10,7 @@ import mega.privacy.android.domain.entity.contacts.AccountCredentials * @property isVerifyingCredentials True if is already verifying credentials, false otherwise. * @property myAccountCredentials [AccountCredentials.MyAccountCredentials]. * @property error String resource id for showing an error. + * @property showContactVerificationBanner Boolean to check if the node is incoming */ data class AuthenticityCredentialsState( val contactCredentials: AccountCredentials.ContactCredentials? = null, @@ -17,4 +18,5 @@ data class AuthenticityCredentialsState( val isVerifyingCredentials: Boolean = false, val myAccountCredentials: AccountCredentials.MyAccountCredentials? = null, val error: Int? = null, + val showContactVerificationBanner: Boolean = false, ) \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt index bbbe2ee4ad1..3af4183c2ca 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/contact/authenticitycredendials/view/AuthenticityCredentialsView.kt @@ -134,7 +134,7 @@ fun ContactCredentials( .background(color = if (MaterialTheme.colors.isLight) white else dark_grey)) { Column { - if (isBannerVisible) { + if (state.showContactVerificationBanner && isBannerVisible) { Box(modifier = Modifier .testTag("CONTACT_VERIFICATION_BANNER_VIEW") .fillMaxWidth() diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt index 3591e9ff805..a54b6f569df 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt @@ -28,12 +28,12 @@ class SecurityUpgradeDialogFragment : DialogFragment() { */ @Inject lateinit var getThemeMode: GetThemeMode - + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = MaterialAlertDialogBuilder(requireContext()).setView( ComposeView(requireContext()).apply { setContent { - val nodeName = arguments?.getStringArrayList("nodeName") as List + val nodeName = arguments?.getStringArrayList("nodeNames") as List val mode by getThemeMode() .collectAsStateWithLifecycle(initialValue = ThemeMode.System) AndroidTheme(isDark = mode.isDarkMode()) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt index 2ba5c4a08dd..a3f22827574 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt @@ -42,7 +42,7 @@ import mega.privacy.android.core.ui.theme.subtitle1 @OptIn(ExperimentalComposeUiApi::class) @Composable fun SecurityUpgradeDialogView( - folderNames: List, + folderNames: List = emptyList(), onOkClick: () -> Unit, onCancelClick: () -> Unit, ) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt index 164f4422097..a1049eb2276 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/recentactions/RecentActionsFragment.kt @@ -32,6 +32,7 @@ import mega.privacy.android.app.fragments.homepage.main.HomepageFragmentDirectio import mega.privacy.android.app.imageviewer.ImageViewerActivity.Companion.getIntentForSingleNode import mega.privacy.android.app.main.ManagerActivity import mega.privacy.android.app.main.PdfViewerActivity +import mega.privacy.android.app.main.controllers.NodeController import mega.privacy.android.app.modalbottomsheet.NodeOptionsBottomSheetDialogFragment import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity import mega.privacy.android.app.presentation.recentactions.model.RecentActionItemType @@ -75,6 +76,7 @@ class RecentActionsFragment : Fragment() { private lateinit var activityHiddenSpanned: Spanned private lateinit var listView: RecyclerView private lateinit var fastScroller: FastScroller + private lateinit var nodeController: NodeController private val viewModel: RecentActionsViewModel by activityViewModels() @@ -89,7 +91,7 @@ class RecentActionsFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - + nodeController = NodeController(requireActivity()) setupView() observeDragSupportEvents(viewLifecycleOwner, listView, Constants.VIEWER_FROM_RECETS) @@ -129,11 +131,17 @@ class RecentActionsFragment : Fragment() { */ private fun initAdapter() { adapter.setOnItemClickListener { item, position -> - if (!item.isKeyVerified) { - Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { - putExtra(Constants.EMAIL, item.bucket.userEmail) - requireActivity().startActivity(this) + lifecycleScope.launch { + viewModel.getMegaNode(item.bucket.nodes[0].id.longValue)?.let { megaNode -> + Intent(requireActivity(), + AuthenticityCredentialsActivity::class.java).apply { + putExtra(Constants.IS_NODE_INCOMING, + nodeController.nodeComesFromIncoming(megaNode)) + putExtra(Constants.EMAIL, item.bucket.userEmail) + requireActivity().startActivity(this) + } + } } } else { // If only one element in the bucket diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 28a3af32822..a142f35eee8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.R import mega.privacy.android.app.components.NewGridRecyclerView import mega.privacy.android.app.main.adapters.MegaNodeAdapter +import mega.privacy.android.app.main.controllers.NodeController import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.manager.model.Tab @@ -50,6 +51,8 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { private fun state() = viewModel.state.value + private lateinit var nodeController: NodeController + /** * onCreateView */ @@ -78,6 +81,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { */ override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + nodeController = NodeController(requireActivity()) setupObservers() } @@ -98,6 +102,8 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { val actualPosition = position - 1 if (state().unVerifiedIncomingNodeHandles.contains(state().nodes[actualPosition].handle)) { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { + putExtra(Constants.IS_NODE_INCOMING, + nodeController.nodeComesFromIncoming(state().nodes[actualPosition])) putExtra(Constants.EMAIL, ContactUtil.getContactEmailDB(state().nodes[actualPosition].owner)) requireActivity().startActivity(this) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index 56ee6aaf12c..4169301854d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -14,7 +14,6 @@ import nz.mega.sdk.MegaNode * @param isInvalidHandle true if parent handle is invalid * @param isLoading true if the nodes are loading * @param sortOrder current sort order - * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedIncomingShares List of unverified incoming [ShareData] * @param unVerifiedIncomingNodeHandles List of unverified incoming node handles */ @@ -26,7 +25,6 @@ data class IncomingSharesState( val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedIncomingShares: List = emptyList(), val unVerifiedIncomingNodeHandles: List = emptyList(), ) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index b326a7e9987..64b886a5841 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -14,7 +14,6 @@ import nz.mega.sdk.MegaNode * @param isInvalidHandle true if handle is invalid * @param isLoading true if the nodes are loading * @param sortOrder current sort order - * @param isMandatoryFingerprintVerificationNeeded Boolean to get if mandatory finger print verification Needed * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles */ @@ -26,7 +25,6 @@ data class OutgoingSharesState( val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val isMandatoryFingerprintVerificationNeeded: Boolean = false, val unverifiedOutgoingShares: List = emptyList(), val unVerifiedOutgoingNodeHandles: List = emptyList(), ) { diff --git a/app/src/main/java/mega/privacy/android/app/utils/Constants.java b/app/src/main/java/mega/privacy/android/app/utils/Constants.java index 663849b3b3f..9fc44978796 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/Constants.java +++ b/app/src/main/java/mega/privacy/android/app/utils/Constants.java @@ -584,6 +584,7 @@ public class Constants { public static final int SCROLLING_UP_DIRECTION = -1; public static final int REQUIRE_PASSCODE_INVALID = -1; + public static final String IS_NODE_INCOMING = "isNodeIncoming"; public static final String CONTACT_HANDLE = "contactHandle"; public static final String SHOW_SNACKBAR = "SHOW_SNACKBAR"; public static final String CHAT_ID = "CHAT_ID"; diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt index 053860cf6c5..4297d92fe53 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/contact/authenticitycredendials/AuthenticityCredentialsViewTest.kt @@ -149,7 +149,8 @@ class AuthenticityCredentialsViewTest { initComposeRuleContent(AuthenticityCredentialsState( contactCredentials = contactCredentials, myAccountCredentials = AccountCredentials.MyAccountCredentials(myCredentials), - )) + showContactVerificationBanner = true, + )) composeTestRule.onNodeWithTag("CONTACT_VERIFICATION_BANNER_VIEW").assertExists() composeTestRule.onNodeWithText(R.string.shared_items_verify_credentials_verify_person_banner_label) .assertExists() From 2466588b9385e2bb133b545f8903b873181252a8 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 23 Feb 2023 05:54:15 +1300 Subject: [PATCH 217/334] AND-15739 - Race condition potential bug fixed in ManagerActivity --- .../assets/featuretoggle/feature_flags.json | 4 +- .../assets/featuretoggle/feature_flags.json | 4 ++ .../android/app/main/ManagerActivity.java | 2 +- .../SecurityUpgradeDialogView.kt | 63 ++++++++++++------- .../assets/featuretoggle/feature_flags.json | 4 -- 5 files changed, 46 insertions(+), 31 deletions(-) diff --git a/app/src/debug/assets/featuretoggle/feature_flags.json b/app/src/debug/assets/featuretoggle/feature_flags.json index 676a4c1439e..25abad8e4c3 100644 --- a/app/src/debug/assets/featuretoggle/feature_flags.json +++ b/app/src/debug/assets/featuretoggle/feature_flags.json @@ -4,7 +4,7 @@ "value": true }, { - "name": "MandatoryFingerprintVerification", - "value": true + "name": "SetSecureFlag", + "value": false } ] \ No newline at end of file diff --git a/app/src/main/assets/featuretoggle/feature_flags.json b/app/src/main/assets/featuretoggle/feature_flags.json index 55e3a8e16d2..5f8aa914f37 100644 --- a/app/src/main/assets/featuretoggle/feature_flags.json +++ b/app/src/main/assets/featuretoggle/feature_flags.json @@ -2,5 +2,9 @@ { "name": "AppTest", "value": true + }, + { + "name": "SetSecureFlag", + "value": false } ] \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 210a8230d02..225abeacdac 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -2462,7 +2462,7 @@ private void collectFlows() { }); ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { - if (viewModel.getState().getValue().getShouldAlertUserAboutSecurityUpgrade()) { + if (managerState.getShouldAlertUserAboutSecurityUpgrade()) { ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { outgoingFolderNames.clear(); for (int i = 0; i < outgoingSharesState.getNodes().size(); i++) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt index a3f22827574..f335bd5cfec 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogView.kt @@ -86,45 +86,60 @@ fun SecurityUpgradeDialogView( Spacer(Modifier.height(20.dp)) - Text(modifier = Modifier.testTag("SharedNodeInfo"), - text = pluralStringResource(id = R.plurals.shared_items_security_upgrade_dialog_node_sharing_info, - folderNames.size, TextUtils.join(", ", folderNames)), - style = body2.copy(textAlign = TextAlign.Center), - color = if (MaterialTheme.colors.isLight) { - Color.Black - } else { - Color.White - }) + if (folderNames.isNotEmpty()) { + Text( + modifier = Modifier.testTag("SharedNodeInfo"), + text = pluralStringResource( + id = R.plurals.shared_items_security_upgrade_dialog_node_sharing_info, + folderNames.size, TextUtils.join(", ", folderNames) + ), + style = body2.copy(textAlign = TextAlign.Center), + color = if (MaterialTheme.colors.isLight) { + Color.Black + } else { + Color.White + } + ) - Spacer(Modifier.height(20.dp)) + Spacer(Modifier.height(20.dp)) + + } - Button(modifier = Modifier - .height(45.dp) - .fillMaxWidth() - .padding(start = 25.dp, end = 25.dp), + Button( + modifier = Modifier + .height(45.dp) + .fillMaxWidth() + .padding(start = 25.dp, end = 25.dp), shape = RoundedCornerShape(8.dp), content = { - Text(text = stringResource(id = R.string.general_ok), - color = Color.White) + Text( + text = stringResource(id = R.string.general_ok), + color = Color.White + ) }, colors = ButtonDefaults.buttonColors(backgroundColor = jade_300), - onClick = onOkClick) + onClick = onOkClick + ) Spacer(Modifier.height(10.dp)) - Button(modifier = Modifier - .height(45.dp) - .fillMaxWidth() - .padding(start = 25.dp, end = 25.dp), + Button( + modifier = Modifier + .height(45.dp) + .fillMaxWidth() + .padding(start = 25.dp, end = 25.dp), shape = RoundedCornerShape(8.dp), colors = ButtonDefaults.buttonColors(backgroundColor = if (MaterialTheme.colors.isLight) Color.White else Color.DarkGray), - onClick = onCancelClick) { - Text(text = stringResource(id = R.string.button_cancel), + onClick = onCancelClick + ) { + Text( + text = stringResource(id = R.string.button_cancel), color = if (MaterialTheme.colors.isLight) { Color.Black } else { jade_300 - }) + } + ) } }) }) diff --git a/app/src/qa/assets/featuretoggle/feature_flags.json b/app/src/qa/assets/featuretoggle/feature_flags.json index 903fef89531..25abad8e4c3 100644 --- a/app/src/qa/assets/featuretoggle/feature_flags.json +++ b/app/src/qa/assets/featuretoggle/feature_flags.json @@ -3,10 +3,6 @@ "name": "PermanentLogging", "value": true }, - { - "name": "MandatoryFingerprintVerification", - "value": true - }, { "name": "SetSecureFlag", "value": false From 543f6fd23987fb1d2d5742dd8e55f4dc486c4327 Mon Sep 17 00:00:00 2001 From: Dhananjay Kulkarni Date: Thu, 23 Feb 2023 13:01:48 +0530 Subject: [PATCH 218/334] AND-15760 Fix share folder failure --- .../NodeOptionsBottomSheetViewModel.kt | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt index 3b483b693d8..ac1522122a9 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetViewModel.kt @@ -32,18 +32,6 @@ class NodeOptionsBottomSheetViewModel @Inject constructor( */ val state: StateFlow = _state - /** - * Check if the handle is valid or not - * - * @param handle - * @return true if the handle is invalid - */ - private suspend fun isInvalidHandle(handle: Long = _state.value.currentNodeHandle): Boolean { - return handle - .takeUnless { it == -1L || it == MegaApiJava.INVALID_HANDLE } - ?.let { getNodeByHandle(it) == null } - ?: true - } /** * Calls OpenShareDialog use case to create crypto key for sharing @@ -53,7 +41,7 @@ class NodeOptionsBottomSheetViewModel @Inject constructor( fun callOpenShareDialog(nodeHandle: Long) { kotlin.runCatching { viewModelScope.launch { - if (!isInvalidHandle(nodeHandle)) { + if (nodeHandle != MegaApiJava.INVALID_HANDLE) { getNodeByHandle(nodeHandle)?.let { megaNode -> openShareDialog(megaNode) } From 192fea513618746b5514802292ea2c14e05f8fb3 Mon Sep 17 00:00:00 2001 From: Yenel Date: Fri, 3 Mar 2023 13:28:32 +0100 Subject: [PATCH 219/334] Merge branch 'master' into release/v7.5 --- sdk/src/main/jni/mega/sdk | 2 +- sdk/src/main/jni/megachat/sdk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index 0b366553889..0bb1b66b527 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit 0b366553889bd21199e081e4951ca0b484d01fb5 +Subproject commit 0bb1b66b527093403a7435a12df2c1107c509c64 diff --git a/sdk/src/main/jni/megachat/sdk b/sdk/src/main/jni/megachat/sdk index 7da39ae3963..6f8ad72568d 160000 --- a/sdk/src/main/jni/megachat/sdk +++ b/sdk/src/main/jni/megachat/sdk @@ -1 +1 @@ -Subproject commit 7da39ae3963d18bbb69db8caf7076ad97ae1d6a7 +Subproject commit 6f8ad72568da4ea062b05030d663f54d08170724 From 2e1278f043615cebb8202613cb9b966df958a9af Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Sun, 5 Mar 2023 15:50:43 +1300 Subject: [PATCH 220/334] AND-15249: Mandatory fingerprint authentication (FIX) - Auto-refresh view --- .../android/app/main/ManagerActivity.java | 51 ++++++++++++++++--- .../presentation/manager/ManagerViewModel.kt | 24 +++++++++ .../shares/incoming/IncomingSharesFragment.kt | 2 +- .../incoming/IncomingSharesViewModel.kt | 47 ++++++++++++----- .../shares/outgoing/OutgoingSharesFragment.kt | 2 +- .../outgoing/OutgoingSharesViewModel.kt | 49 ++++++++++++------ .../manager/ManagerViewModelTest.kt | 4 ++ .../incoming/IncomingSharesViewModelTest.kt | 27 +++++++--- .../outgoing/OutgoingSharesViewModelTest.kt | 4 ++ build.gradle | 4 +- 10 files changed, 168 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index f0c5b4f2246..5aab4d6170c 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -557,6 +557,8 @@ public class ManagerActivity extends TransfersManagementActivity FloatingActionButton fabButton; FloatingActionButton fabMaskButton; + View pendingActionsBadge; + MegaNode rootNode = null; NodeController nC; @@ -2478,13 +2480,7 @@ private void collectFlows() { } // Update pending actions badge on bottom navigation menu - if (managerState.getPendingActionsCount() > 0) { - BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); - View pendingActionsBadge = LayoutInflater.from(this).inflate(R.layout.bottom_pending_actions_badge, menuView, false); - sharedItemsView.addView(pendingActionsBadge); - TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.pending_actions_badge_text); - tvPendingActionsCount.setText(String.valueOf(managerState.getPendingActionsCount())); - } + updateUnverifiedSharesBadge(managerState.getPendingActionsCount()); return Unit.INSTANCE; }); ViewExtensionsKt.collectFlow(this, viewModel.getOnViewTypeChanged(), Lifecycle.State.STARTED, viewType -> { @@ -2521,6 +2517,43 @@ private void collectFlows() { }); } + /** + * Update the unverified shares badge count on the navigation bottom item view + * + * This function ensure that the badge view is added again only if it has not been added previously + * + * @param pendingActionsCount if > 0 add the badge view else remove it + */ + private void updateUnverifiedSharesBadge(int pendingActionsCount) { + if (pendingActionsCount > 0) { + BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); + if (sharedItemsView == null) return; + + if (pendingActionsBadge != null) { + int index = sharedItemsView.indexOfChild(pendingActionsBadge); + if (index != -1) { + sharedItemsView.removeViewAt(index); + } + } else { + pendingActionsBadge = LayoutInflater.from(this) + .inflate(R.layout.bottom_pending_actions_badge, menuView, false); + } + sharedItemsView.addView(pendingActionsBadge); + TextView tvPendingActionsCount = pendingActionsBadge.findViewById(R.id.pending_actions_badge_text); + tvPendingActionsCount.setText(String.valueOf(pendingActionsCount)); + } else { + if (pendingActionsBadge != null) { + BottomNavigationItemView sharedItemsView = (BottomNavigationItemView) menuView.getChildAt(4); + if (sharedItemsView == null) return; + int index = sharedItemsView.indexOfChild(pendingActionsBadge); + if (index != -1) { + sharedItemsView.removeViewAt(index); + } + + } + } + } + /** * Updates the View Type * @@ -10913,6 +10946,8 @@ private void addUnverifiedIncomingCountBadge(int unverifiedNodesCount) { if (incomingSharesTab != null) { if (unverifiedNodesCount > 0) { incomingSharesTab.getOrCreateBadge().setNumber(unverifiedNodesCount); + } else { + incomingSharesTab.removeBadge(); } } } @@ -10925,6 +10960,8 @@ private void addUnverifiedOutgoingCountBadge(int unverifiedNodesCount) { if (outgoingSharesTab != null) { if (unverifiedNodesCount > 0) { outgoingSharesTab.getOrCreateBadge().setNumber(unverifiedNodesCount); + } else { + outgoingSharesTab.removeBadge(); } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index 7b5679d1771..f548ca3592c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.mapNotNull @@ -39,6 +40,7 @@ import mega.privacy.android.domain.entity.contacts.ContactRequest import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.preference.ViewType import mega.privacy.android.domain.qualifier.IoDispatcher +import mega.privacy.android.domain.entity.user.UserChanges import mega.privacy.android.domain.usecase.BroadcastUploadPauseState import mega.privacy.android.domain.usecase.CheckCameraUpload import mega.privacy.android.domain.usecase.GetCloudSortOrder @@ -52,6 +54,7 @@ import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares import mega.privacy.android.domain.usecase.HasInboxChildren import mega.privacy.android.domain.usecase.MonitorConnectivity import mega.privacy.android.domain.usecase.MonitorContactRequestUpdates +import mega.privacy.android.domain.usecase.MonitorContactUpdates import mega.privacy.android.domain.usecase.MonitorMyAvatarFile import mega.privacy.android.domain.usecase.MonitorStorageStateEvent import mega.privacy.android.domain.usecase.SendStatisticsMediaDiscovery @@ -69,6 +72,7 @@ import javax.inject.Inject * * @param monitorNodeUpdates Monitor global node updates * @param monitorGlobalUpdates Monitor global updates + * @param monitorContactUpdates monitor contact update when credentials verification occurs to update shares count * @param monitorContactRequestUpdates * @param getInboxNode * @param getNumUnreadUserAlerts @@ -87,6 +91,7 @@ import javax.inject.Inject @HiltViewModel class ManagerViewModel @Inject constructor( monitorNodeUpdates: MonitorNodeUpdates, + monitorContactUpdates: MonitorContactUpdates, private val monitorGlobalUpdates: MonitorGlobalUpdates, monitorContactRequestUpdates: MonitorContactRequestUpdates, private val getInboxNode: GetInboxNode, @@ -155,6 +160,14 @@ class ManagerViewModel @Inject constructor( checkItemForInbox(nodeList) onReceiveNodeUpdate(true) checkCameraUploadFolder(false, nodeList) + checkUnverifiedSharesCount() + } + } + viewModelScope.launch { + monitorContactUpdates().collectLatest { updates -> + if (updates.changes.values.any { it.contains(UserChanges.AuthenticationInformation) }) { + checkUnverifiedSharesCount() + } } } viewModelScope.launch(ioDispatcher) { @@ -280,6 +293,17 @@ class ManagerViewModel @Inject constructor( onReceiveNodeUpdate(false) } + /** + * Get the unverified shares count and set state + */ + private suspend fun checkUnverifiedSharesCount() { + val sortOrder = getCloudSortOrder() + val unverifiedIncomingShares = getUnverifiedIncomingShares(sortOrder).size + val unverifiedOutgoingShares = + getUnverifiedOutgoingShares(sortOrder).filter { shareData -> !shareData.isVerified }.size + _state.update { it.copy(pendingActionsCount = unverifiedIncomingShares + unverifiedOutgoingShares) } + } + /** * Set the ui one-off event when a node update is received * diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index a142f35eee8..4d01ab45820 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -254,9 +254,9 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() - setEmptyView(it.isInvalidHandle) adapter?.setUnverifiedIncomingNodeHandles(it.unVerifiedIncomingNodeHandles) updateNodes(it.nodes) + setEmptyView(it.isInvalidHandle) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 5f52f9ac7b6..4cdda538230 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.AuthorizeNode @@ -12,10 +13,12 @@ import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates import mega.privacy.android.app.presentation.shares.incoming.model.IncomingSharesState +import mega.privacy.android.domain.entity.user.UserChanges import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.GetUnverifiedIncomingShares +import mega.privacy.android.domain.usecase.MonitorContactUpdates import nz.mega.sdk.MegaApiJava.INVALID_HANDLE import nz.mega.sdk.MegaNode import timber.log.Timber @@ -24,6 +27,8 @@ import javax.inject.Inject /** * ViewModel associated to IncomingSharesFragment + * + * @param monitorContactUpdates monitor contact update when credentials verification occurs to update shares list */ @HiltViewModel class IncomingSharesViewModel @Inject constructor( @@ -34,6 +39,7 @@ class IncomingSharesViewModel @Inject constructor( private val getCloudSortOrder: GetCloudSortOrder, private val getOthersSortOrder: GetOthersSortOrder, monitorNodeUpdates: MonitorNodeUpdates, + monitorContactUpdates: MonitorContactUpdates, private val getUnverifiedIncomingShares: GetUnverifiedIncomingShares, ) : ViewModel() { @@ -47,8 +53,9 @@ class IncomingSharesViewModel @Inject constructor( private val lastPositionStack: Stack = Stack() init { + refreshIncomingSharesNode() + viewModelScope.launch { - refreshNodes()?.let { setNodes(it) } monitorNodeUpdates().collect { list -> Timber.d("Received node update") // If the current incoming handle is the node that was updated, @@ -64,19 +71,15 @@ class IncomingSharesViewModel @Inject constructor( resetIncomingTreeDepth() } } - refreshNodes()?.let { setNodes(it) } + refreshIncomingSharesNode() } } - viewModelScope.launch { - val unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder) - .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - val handles = unverifiedIncomingShares.map { shareData -> shareData.nodeHandle } - val unverifiedIncomingNodes = handles.mapNotNull { handle -> getNodeByHandle(handle) } - _state.update { - it.copy(nodes = unverifiedIncomingNodes, - unverifiedIncomingShares = unverifiedIncomingShares, - unVerifiedIncomingNodeHandles = handles) + monitorContactUpdates().collectLatest { updates -> + Timber.d("Received contact update") + if (updates.changes.values.any { it.contains(UserChanges.AuthenticationInformation) }) { + refreshIncomingSharesNode() + } } } } @@ -186,7 +189,25 @@ class IncomingSharesViewModel @Inject constructor( */ private suspend fun refreshNodes(handle: Long = _state.value.incomingHandle): List? { Timber.d("refreshIncomingSharesNodes") - return getIncomingSharesChildrenNode(handle) + val unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder) + .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + val handles = unverifiedIncomingShares.map { shareData -> shareData.nodeHandle } + _state.update { + it.copy( + unverifiedIncomingShares = unverifiedIncomingShares, + unVerifiedIncomingNodeHandles = handles + ) + } + + val unverifiedIncomingSharesNodes = handles.mapNotNull { getNodeByHandle(it) } + val incomingSharesNodes = getIncomingSharesChildrenNode(handle) + return if ( + unverifiedIncomingSharesNodes.isNotEmpty() || incomingSharesNodes.isNullOrEmpty().not() + ) + mutableListOf().apply { + addAll(unverifiedIncomingSharesNodes) + incomingSharesNodes?.let { addAll(it) } + }.distinctBy { it.handle } else null } /** @@ -201,4 +222,4 @@ class IncomingSharesViewModel @Inject constructor( ?.let { getNodeByHandle(it) == null } ?: true } -} \ No newline at end of file +} diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 1a71f90bd7a..0742e937411 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -245,10 +245,10 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() - setEmptyView(it.isInvalidHandle) adapter?.setUnverifiedOutgoingShareData(it.unverifiedOutgoingShares) adapter?.setUnverifiedOutgoingNodeHandles(it.unVerifiedOutgoingNodeHandles) updateNodes(it.nodes) + setEmptyView(it.isInvalidHandle) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index a2a75f8d293..3f0a0a8fedb 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -5,16 +5,19 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates import mega.privacy.android.app.presentation.shares.outgoing.model.OutgoingSharesState +import mega.privacy.android.domain.entity.user.UserChanges import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle import mega.privacy.android.domain.usecase.GetUnverifiedOutgoingShares +import mega.privacy.android.domain.usecase.MonitorContactUpdates import nz.mega.sdk.MegaApiJava import nz.mega.sdk.MegaNode import timber.log.Timber @@ -23,6 +26,8 @@ import javax.inject.Inject /** * ViewModel associated to OutgoingSharesFragment + * + * @param monitorContactUpdates monitor contact update when credentials verification occurs to update shares list */ @HiltViewModel class OutgoingSharesViewModel @Inject constructor( @@ -32,6 +37,7 @@ class OutgoingSharesViewModel @Inject constructor( private val getCloudSortOrder: GetCloudSortOrder, private val getOthersSortOrder: GetOthersSortOrder, monitorNodeUpdates: MonitorNodeUpdates, + monitorContactUpdates: MonitorContactUpdates, private val getUnverifiedOutgoingShares: GetUnverifiedOutgoingShares, ) : ViewModel() { @@ -45,25 +51,20 @@ class OutgoingSharesViewModel @Inject constructor( private val lastPositionStack: Stack = Stack() init { + refreshOutgoingSharesNode() + viewModelScope.launch { - refreshNodes()?.let { setNodes(it) } - monitorNodeUpdates().collect { + monitorNodeUpdates().collectLatest { Timber.d("Received node update") - refreshNodes()?.let { setNodes(it) } + refreshOutgoingSharesNode() } } - viewModelScope.launch { - val unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder) - .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .filter { shareData -> !shareData.isVerified } - val handles = unverifiedOutgoingShares - .map { shareData -> shareData.nodeHandle } - _state.update { - it.copy( - unverifiedOutgoingShares = unverifiedOutgoingShares, - unVerifiedOutgoingNodeHandles = handles - ) + monitorContactUpdates().collectLatest { updates -> + Timber.d("Received contact update") + if (updates.changes.values.any { it.contains(UserChanges.AuthenticationInformation) }) { + refreshOutgoingSharesNode() + } } } } @@ -73,6 +74,7 @@ class OutgoingSharesViewModel @Inject constructor( */ fun refreshOutgoingSharesNode() = viewModelScope.launch { refreshNodes()?.let { setNodes(it) } + refreshUnverifiedOutgoingSharesNodes() } /** @@ -176,6 +178,23 @@ class OutgoingSharesViewModel @Inject constructor( return getOutgoingSharesChildrenNode(handle) } + /** + * Refresh unverified outgoing shares + */ + private suspend fun refreshUnverifiedOutgoingSharesNodes() { + val unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder) + .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + .filter { shareData -> !shareData.isVerified } + val handles = unverifiedOutgoingShares + .map { shareData -> shareData.nodeHandle } + _state.update { + it.copy( + unverifiedOutgoingShares = unverifiedOutgoingShares, + unVerifiedOutgoingNodeHandles = handles + ) + } + } + /** * Check if the handle is valid or not * @@ -195,4 +214,4 @@ class OutgoingSharesViewModel @Inject constructor( fun getUnVerifiedOutgoingNodeShare(handle: Long?) = state.value.unverifiedOutgoingShares.find { node -> node.nodeHandle == handle } -} \ No newline at end of file +} diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index a9647f50782..8e143b15dff 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -7,6 +7,7 @@ import com.google.common.truth.Truth.assertThat import com.jraska.livedata.test import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.distinctUntilChanged @@ -30,6 +31,7 @@ import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.contacts.ContactRequest import mega.privacy.android.domain.entity.contacts.ContactRequestStatus import mega.privacy.android.domain.entity.node.NodeUpdate +import mega.privacy.android.domain.entity.user.UserUpdate import mega.privacy.android.domain.entity.verification.UnVerified import mega.privacy.android.domain.entity.verification.VerificationStatus import mega.privacy.android.domain.usecase.CheckCameraUpload @@ -60,6 +62,7 @@ class ManagerViewModelTest { private val monitorGlobalUpdates = mock() private val monitorNodeUpdates = FakeMonitorUpdates() + private val monitorContactUpdates = MutableSharedFlow() private val getNumUnreadUserAlerts = mock() private val hasInboxChildren = mock() private val monitorContactRequestUpdates = mock() @@ -121,6 +124,7 @@ class ManagerViewModelTest { underTest = ManagerViewModel( monitorNodeUpdates = monitorNodeUpdates, monitorGlobalUpdates = monitorGlobalUpdates, + monitorContactUpdates = { monitorContactUpdates }, monitorContactRequestUpdates = monitorContactRequestUpdates, getNumUnreadUserAlerts = getNumUnreadUserAlerts, hasInboxChildren = hasInboxChildren, diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 73ac6ceb819..4ba386d311e 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -5,6 +5,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -21,6 +22,7 @@ import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeChanges import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate +import mega.privacy.android.domain.entity.user.UserUpdate import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle @@ -52,13 +54,13 @@ class IncomingSharesViewModelTest { onBlocking { invoke() }.thenReturn(SortOrder.ORDER_DEFAULT_DESC) } private val monitorNodeUpdates = FakeMonitorUpdates() + private val monitorContactUpdates = MutableSharedFlow() @get:Rule var instantExecutorRule = InstantTaskExecutorRule() private val getUnverifiedIncomingShares = mock { - val shareData = ShareData("user", 8766L, 0, 987654678L, true, false) - onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) + onBlocking { invoke(any()) }.thenReturn(listOf()) } @Before @@ -81,6 +83,7 @@ class IncomingSharesViewModelTest { getCloudSortOrder, getOtherSortOrder, monitorNodeUpdates, + { monitorContactUpdates }, getUnverifiedIncomingShares, ) } @@ -337,8 +340,12 @@ class IncomingSharesViewModelTest { @Test fun `test that nodes is set with result of getIncomingSharesChildrenNode if not null`() = runTest { - val node1 = mock() - val node2 = mock() + val node1 = mock { + on { this.handle }.thenReturn(1234L) + } + val node2 = mock() { + on { this.handle }.thenReturn(5678L) + } val expected = listOf(node1, node2) whenever(getIncomingSharesChildrenNode(any())).thenReturn(expected) @@ -354,8 +361,12 @@ class IncomingSharesViewModelTest { @Test fun `test that nodes is empty if result of getIncomingSharesChildrenNode null`() = runTest { - val node1 = mock() - val node2 = mock() + val node1 = mock { + on { this.handle }.thenReturn(1234L) + } + val node2 = mock() { + on { this.handle }.thenReturn(5678L) + } val expected = listOf(node1, node2) whenever(getIncomingSharesChildrenNode(123456789L)).thenReturn(expected) @@ -522,10 +533,12 @@ class IncomingSharesViewModelTest { val node1 = mock() whenever(getNodeByHandle(any())).thenReturn(node1) assertThat(getNodeByHandle(any())).isNotNull() + val expected = ShareData("user", 8766L, 0, 987654678L, true, false) + whenever(getUnverifiedIncomingShares(any())).thenReturn(listOf(expected)) initViewModel() underTest.state.map { it.unverifiedIncomingShares }.distinctUntilChanged() .test { assertThat(awaitItem().size).isEqualTo(1) } } -} \ No newline at end of file +} diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index 6baad190340..782989a55ae 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -5,6 +5,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.UnconfinedTestDispatcher @@ -18,6 +19,7 @@ import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate +import mega.privacy.android.domain.entity.user.UserUpdate import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle @@ -47,6 +49,7 @@ class OutgoingSharesViewModelTest { onBlocking { invoke() }.thenReturn(SortOrder.ORDER_DEFAULT_DESC) } private val monitorNodeUpdates = FakeMonitorUpdates() + private val monitorContactUpdates = MutableSharedFlow() @get:Rule var instantExecutorRule = InstantTaskExecutorRule() @@ -70,6 +73,7 @@ class OutgoingSharesViewModelTest { getCloudSortOrder, getOtherSortOrder, monitorNodeUpdates, + { monitorContactUpdates }, getUnverifiedOutgoingShares, ) } diff --git a/build.gradle b/build.gradle index 86cabdf7e01..85df68708e2 100644 --- a/build.gradle +++ b/build.gradle @@ -69,8 +69,8 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230228.235733-rel' - + megaSdkVersion = '20230304.032141-rel' + // App dependencies accompanistLayoutVersion = '0.24.13-rc' accompanistVersion ='0.24.13-rc' From b9b7ae82f150c588c7babcba0e034f10efdd2f04 Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Sun, 5 Mar 2023 23:02:15 +1300 Subject: [PATCH 221/334] AND-15249: Mandatory fingerprint authentication (FIX) - Outgoing --- .../android/app/main/ManagerActivity.java | 18 +++- .../app/main/adapters/MegaNodeAdapter.java | 86 ++++++++++++------- .../NodeOptionsBottomSheetDialogFragment.java | 55 +++++++----- .../shares/outgoing/OutgoingSharesFragment.kt | 58 ++++++------- .../outgoing/OutgoingSharesViewModel.kt | 54 ++++++------ .../outgoing/model/OutgoingSharesState.kt | 11 +-- .../outgoing/OutgoingSharesViewModelTest.kt | 35 ++------ 7 files changed, 173 insertions(+), 144 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 5aab4d6170c..c758a9d72b4 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -246,6 +246,7 @@ import java.util.Collections; import java.util.List; import java.util.ListIterator; +import java.util.stream.Collectors; import javax.inject.Inject; @@ -411,6 +412,7 @@ import mega.privacy.android.data.model.MegaAttributes; import mega.privacy.android.data.model.MegaPreferences; import mega.privacy.android.domain.entity.Product; +import mega.privacy.android.domain.entity.ShareData; import mega.privacy.android.domain.entity.StorageState; import mega.privacy.android.domain.entity.contacts.ContactRequest; import mega.privacy.android.domain.entity.contacts.ContactRequestStatus; @@ -571,6 +573,7 @@ public class ManagerActivity extends TransfersManagementActivity private AndroidCompletedTransfer selectedTransfer; MegaNode selectedNode; + ShareData selectedShareData; MegaOffline selectedOfflineNode; MegaContactAdapter selectedUser; MegaContactRequest selectedRequest; @@ -2449,7 +2452,7 @@ private void collectFlows() { ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { outgoingFolderNames.clear(); for (int i = 0; i < outgoingSharesState.getNodes().size(); i++) { - outgoingFolderNames.add(outgoingSharesState.getNodes().get(i).getName()); + outgoingFolderNames.add(outgoingSharesState.getNodes().get(i).getFirst().getName()); } SecurityUpgradeDialogFragment dialog = SecurityUpgradeDialogFragment.Companion.newInstance(); Bundle bundle = new Bundle(); @@ -2512,7 +2515,7 @@ private void collectFlows() { }); ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { - addUnverifiedOutgoingCountBadge(outgoingSharesState.getUnverifiedOutgoingShares().size()); + addUnverifiedOutgoingCountBadge(outgoingSharesState.getNodes().stream().filter(pair -> pair.getSecond() != null).collect(Collectors.toList()).size()); return Unit.INSTANCE; }); } @@ -7182,15 +7185,20 @@ public void showManageTransferOptionsPanel(AndroidCompletedTransfer transfer) { } public void showNodeOptionsPanel(MegaNode node) { - showNodeOptionsPanel(node, NodeOptionsBottomSheetDialogFragment.DEFAULT_MODE); + showNodeOptionsPanel(node, NodeOptionsBottomSheetDialogFragment.DEFAULT_MODE, null); } public void showNodeOptionsPanel(MegaNode node, int mode) { + showNodeOptionsPanel(node, mode, null); + } + + public void showNodeOptionsPanel(MegaNode node, int mode, ShareData shareData) { Timber.d("showNodeOptionsPanel"); if (node == null || isBottomSheetDialogShown(bottomSheetDialogFragment)) return; selectedNode = node; + selectedShareData = shareData; bottomSheetDialogFragment = new NodeOptionsBottomSheetDialogFragment(mode); bottomSheetDialogFragment.show(getSupportFragmentManager(), bottomSheetDialogFragment.getTag()); } @@ -9889,6 +9897,10 @@ public MegaNode getSelectedNode() { return selectedNode; } + public ShareData getSelectedShareData() { + return selectedShareData; + } + public void setSelectedNode(MegaNode selectedNode) { this.selectedNode = selectedNode; } diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index e3b6f9a36e5..5607f569bf6 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -44,6 +44,7 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.util.DisplayMetrics; +import android.util.Pair; import android.util.SparseBooleanArray; import android.util.TypedValue; import android.view.Display; @@ -71,6 +72,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import mega.privacy.android.app.MegaApplication; import mega.privacy.android.app.MimeTypeList; @@ -88,6 +90,7 @@ import mega.privacy.android.app.main.DrawerItem; import mega.privacy.android.app.main.ManagerActivity; import mega.privacy.android.app.main.contactSharedFolder.ContactSharedFolderFragment; +import mega.privacy.android.app.modalbottomsheet.NodeOptionsBottomSheetDialogFragment; import mega.privacy.android.app.presentation.clouddrive.FileBrowserFragment; import mega.privacy.android.app.presentation.folderlink.FolderLinkActivity; import mega.privacy.android.app.presentation.inbox.InboxFragment; @@ -121,6 +124,15 @@ public class MegaNodeAdapter extends RecyclerView.Adapter nodes; + /** + * List of shareData associated to the List of MegaNode + * This list is used to carry additional information for incoming shares and outgoing shares + * Each ShareData element at a specific position is associated to the MegaNode element + * at the same position of the nodes attributes + * The element is null if the node associated is already verified + */ + private List shareData; + private Object fragment; private long parentHandle = -1; private DisplayMetrics outMetrics; @@ -147,9 +159,6 @@ public class MegaNodeAdapter extends RecyclerView.Adapter unverifiedIncomingNodeHandles = new HashSet<>(); - private final Set unverifiedOutgoingNodeHandles = new HashSet<>(); - - private final List unverifiedOutGoingShareData = new ArrayList<>(); public static class ViewHolderBrowser extends RecyclerView.ViewHolder { @@ -647,6 +656,25 @@ public void setNodes(List nodes) { notifyDataSetChanged(); } + /** + * Set the nodes list and shareData list + * This function is used to populate the list of incoming and outgoing shares + * + * @param nodes the list of nodes, whether verified or unverified + * @param shareData the list of shares data associated to the node + */ + public void setNodesWithShareData(List nodes, List shareData) { + this.nodes = insertPlaceHolderNode(nodes); + // need to add extra elements to sharedata too, so that the element at a specific position + // corresponds exactly to the node in the nodes list + for (int i = 0; i < this.nodes.size() - shareData.size(); i ++) { + shareData.add(0, null); + } + this.shareData = shareData; + Timber.d("setNodes size: %s", this.nodes.size()); + notifyDataSetChanged(); + } + /** * Method to update an item when some contact information has changed. * @@ -1095,7 +1123,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { boolean hasUnverifiedNodes = !unverifiedIncomingNodeHandles.isEmpty() && unverifiedIncomingNodeHandles.contains(node.getHandle()); if (hasUnverifiedNodes) { - showUnverifiedNodeUi(holder, true, node); + showUnverifiedNodeUi(holder, true, node, null); } holder.permissionsIcon.setVisibility(View.VISIBLE); } else { @@ -1105,10 +1133,9 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else if (type == OUTGOING_SHARES_ADAPTER) { //Show the number of contacts who shared the folder if more than one contact and name of contact if that is not the case holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - boolean hasUnverifiedNodes = !unverifiedOutgoingNodeHandles.isEmpty() - && unverifiedOutgoingNodeHandles.contains(node.getHandle()); + boolean hasUnverifiedNodes = shareData != null && shareData.get(position) != null; if (hasUnverifiedNodes) { - showUnverifiedNodeUi(holder, false, node); + showUnverifiedNodeUi(holder, false, node, shareData.get(position)); } } } else { @@ -1245,6 +1272,10 @@ public void onClick(View v) { } final MegaNode n = getItem(currentPosition); + ShareData sd = null; + if (shareData != null) { + sd = shareData.get(currentPosition); + } if (n == null) { return; } @@ -1254,7 +1285,7 @@ public void onClick(View v) { case R.id.file_list_three_dots_layout: case R.id.file_grid_three_dots: case R.id.file_grid_three_dots_for_file: { - threeDotsClicked(currentPosition, n); + threeDotsClicked(currentPosition, n, sd); break; } case R.id.file_list_item_layout: @@ -1305,7 +1336,7 @@ private void fileClicked(int currentPosition) { } } - private void threeDotsClicked(int currentPosition, MegaNode n) { + private void threeDotsClicked(int currentPosition, MegaNode n, ShareData sd) { Timber.d("onClick: file_list_three_dots: %s", currentPosition); if (isOffline(context)) { return; @@ -1342,7 +1373,7 @@ private void threeDotsClicked(int currentPosition, MegaNode n) { } else if (type == CONTACT_SHARED_FOLDER_ADAPTER) { ((ContactSharedFolderFragment) fragment).showOptionsPanel(n); } else { - ((ManagerActivity) context).showNodeOptionsPanel(n); + ((ManagerActivity) context).showNodeOptionsPanel(n, NodeOptionsBottomSheetDialogFragment.DEFAULT_MODE, sd); } } } @@ -1449,8 +1480,13 @@ public void setListFragment(RecyclerView listFragment) { private String getOutgoingSubtitle(String currentSubtitle, MegaNode node) { String subtitle = currentSubtitle; - ArrayList sl = megaApi.getOutShares(node); - if (sl != null && sl.size() != 0) { + // only count the outgoing shares that has been verified + List sl = megaApi + .getOutShares(node) + .stream() + .filter(MegaShare::isVerified) + .collect(Collectors.toList()); + if (sl.size() != 0) { if (sl.size() == 1 && sl.get(0).getUser() != null) { subtitle = sl.get(0).getUser(); MegaContactDB contactDB = dbH.findContactByEmail(subtitle); @@ -1530,21 +1566,6 @@ public void setUnverifiedIncomingNodeHandles(List handles) { unverifiedIncomingNodeHandles.addAll(handles); } - /** - * Adds unverified outgoing nodes to Set - * - * @param handles - List of outgoing node handles - */ - public void setUnverifiedOutgoingNodeHandles(List handles) { - unverifiedOutgoingNodeHandles.clear(); - unverifiedOutgoingNodeHandles.addAll(handles); - } - - public void setUnverifiedOutgoingShareData(List shareDataList) { - unverifiedOutGoingShareData.clear(); - unverifiedOutGoingShareData.addAll(shareDataList); - } - /** * Function to show Unverified node UI items accordingly * @@ -1552,7 +1573,7 @@ public void setUnverifiedOutgoingShareData(List shareDataList) { * @param isIncomingNode boolean to indicate if the node is incoming so that * "Undecrypted folder" is displayed instead of node name */ - private void showUnverifiedNodeUi(ViewHolderBrowserList holder, Boolean isIncomingNode, MegaNode node) { + private void showUnverifiedNodeUi(ViewHolderBrowserList holder, Boolean isIncomingNode, MegaNode node, ShareData shareData) { if (isIncomingNode) { if(node.isNodeKeyDecrypted()) { holder.textViewFileName.setText(node.getName()); @@ -1560,10 +1581,11 @@ private void showUnverifiedNodeUi(ViewHolderBrowserList holder, Boolean isIncomi holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); } } else { - for (int i = 0; i < unverifiedOutGoingShareData.size(); i++) { - if(unverifiedOutGoingShareData.get(i).getNodeHandle() == (node.getHandle()) && unverifiedOutGoingShareData.get(i).isPending()) { - holder.textViewFileSize.setText(getOutgoingSubtitle(holder.textViewFileSize.getText().toString(), node)); - } + MegaUser user = megaApi.getContact(shareData.getUser()); + if (user != null) { + holder.textViewFileSize.setText(getMegaUserNameDB(user)); + } else { + holder.textViewFileSize.setText(shareData.getUser()); } } holder.textViewFileName.setTextColor(ContextCompat.getColor(context, R.color.red_600)); diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index 4b17e2bef25..a87f0717b63 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -79,7 +79,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; +import kotlin.Pair; import kotlin.Unit; import mega.privacy.android.app.MegaOffline; import mega.privacy.android.app.MimeTypeList; @@ -166,6 +168,8 @@ public class NodeOptionsBottomSheetDialogFragment extends BaseBottomSheetDialogF private int mMode; private MegaNode node = null; + + private ShareData shareData = null; private NodeController nC; private TextView nodeInfo; @@ -179,7 +183,6 @@ public class NodeOptionsBottomSheetDialogFragment extends BaseBottomSheetDialogF private MegaUser user; private IncomingSharesViewModel incomingSharesViewModel; - private OutgoingSharesViewModel outgoingSharesViewModel; private NodeOptionsBottomSheetViewModel nodeOptionsBottomSheetViewModel; public NodeOptionsBottomSheetDialogFragment(int mode) { @@ -197,7 +200,6 @@ public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class); incomingSharesViewModel = new ViewModelProvider(requireActivity()).get(IncomingSharesViewModel.class); - outgoingSharesViewModel = new ViewModelProvider(requireActivity()).get(OutgoingSharesViewModel.class); nodeOptionsBottomSheetViewModel = new ViewModelProvider(this).get(NodeOptionsBottomSheetViewModel.class); } @@ -216,6 +218,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c } else { if (requireActivity() instanceof ManagerActivity) { node = ((ManagerActivity) requireActivity()).getSelectedNode(); + shareData = ((ManagerActivity) requireActivity()).getSelectedShareData(); drawerItem = ((ManagerActivity) requireActivity()).getDrawerItem(); } } @@ -652,8 +655,10 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat if (!isTakenDown && ((ManagerActivity) requireActivity()).getDeepBrowserTreeOutgoing() == FIRST_NAVIGATION_LEVEL && ViewUtils.isVisible(optionClearShares)) { //Show the number of contacts who shared the folder - ArrayList sl = megaApi.getOutShares(node); - if (sl != null && sl.size() != 0) { + List sl = + megaApi.getOutShares(node) + .stream().filter(MegaShare::isVerified).collect(Collectors.toList()); + if (sl.size() != 0) { nodeInfo.setText(getQuantityString(R.plurals.general_num_shared_with, sl.size(), sl.size())); } @@ -726,22 +731,18 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat optionLabelCurrent.setVisibility(View.GONE); } + if(shareData != null) { + setUnverifiedOutgoingNodeUserName(shareData); + hideNodeActions(shareData); + } + super.onViewCreated(view, savedInstanceState); if(nC.nodeComesFromIncoming(node)) { ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { if (mMode == SHARED_ITEMS_MODE && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { setUnverifiedNodeUserName(state.getUnverifiedIncomingShares()); - hideNodeActions(); - } - return Unit.INSTANCE; - }); - } else { - ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (mMode == SHARED_ITEMS_MODE - && isNodeUnverified(state.getUnVerifiedOutgoingNodeHandles())) { - setUnverifiedNodeUserName(state.getUnverifiedOutgoingShares()); - hideNodeActions(); + hideNodeActions(shareData); } return Unit.INSTANCE; }); @@ -899,6 +900,19 @@ private void showOwnerSharedFolder() { } } + /** + * Set the node info of the unverified node with the name of the contact + * @param shareData + */ + private void setUnverifiedOutgoingNodeUserName(ShareData shareData) { + user = megaApi.getContact(shareData.getUser()); + if (user != null) { + nodeInfo.setText(getMegaUserNameDB(user)); + } else { + nodeInfo.setText(shareData.getUser()); + } + + } private void setUnverifiedNodeUserName(List shareDataList) { for (int j = 0; j < shareDataList.size(); j++) { ShareData mS = shareDataList.get(j); @@ -914,17 +928,20 @@ private void setUnverifiedNodeUserName(List shareDataList) { } } - private void hideNodeActions() { + private void hideNodeActions(ShareData shareData) { + contentView.findViewById(R.id.verify_user_option).setVisibility(View.VISIBLE); TextView optionVerifyUser = contentView.findViewById(R.id.verify_user_option); - optionVerifyUser.setText(StringResourcesUtils.getString(R.string.shared_items_bottom_sheet_menu_verify_user, getMegaUserNameDB(user))); + if (shareData!= null && !shareData.isVerified()) { + optionVerifyUser.setText(getString(R.string.shared_items_bottom_sheet_menu_verify_user, nodeInfo.getText())); + } + optionVerifyUser.setOnClickListener(this); + TextView nodeName = contentView.findViewById(R.id.node_name_text); if(nC.nodeComesFromIncoming(node)) { nodeName.setText(getResources().getString(R.string.shared_items_verify_credentials_undecrypted_folder)); } else { nodeName.setText(node.getName()); } - optionVerifyUser.setVisibility(View.VISIBLE); - optionVerifyUser.setOnClickListener(this); contentView.findViewById(R.id.favorite_option).setVisibility(View.GONE); contentView.findViewById(R.id.rename_option).setVisibility(View.GONE); @@ -935,7 +952,6 @@ private void hideNodeActions() { contentView.findViewById(R.id.copy_option).setVisibility(View.GONE); contentView.findViewById(R.id.rubbish_bin_option).setVisibility(View.GONE); contentView.findViewById(R.id.share_option).setVisibility(View.GONE); - contentView.findViewById(R.id.share_folder_option).setVisibility(View.GONE); contentView.findViewById(R.id.clear_share_option).setVisibility(View.GONE); } @@ -1108,7 +1124,6 @@ public void onClick(View v) { requireActivity().startActivityForResult(version, REQUEST_CODE_DELETE_VERSIONS_HISTORY); break; case R.id.verify_user_option: - ShareData shareData = outgoingSharesViewModel.getUnVerifiedOutgoingNodeShare(node.getHandle()); if (shareData != null) { if (!shareData.isVerified() && shareData.isPending()) { showCanNotVerifyContact(shareData.getUser()); diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 0742e937411..c1058e09296 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -32,6 +32,7 @@ import mega.privacy.android.app.utils.MegaNodeUtil.areAllNotTakenDown import mega.privacy.android.app.utils.MegaNodeUtil.canMoveToRubbish import mega.privacy.android.app.utils.StringResourcesUtils import mega.privacy.android.app.utils.Util +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import nz.mega.sdk.MegaApiJava.INVALID_HANDLE import nz.mega.sdk.MegaError @@ -95,36 +96,36 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { override fun itemClick(position: Int) { val actualPosition = position - 1 - val clickedNodeHandle = adapter?.getItem(position)?.handle - val shareData = viewModel.getUnVerifiedOutgoingNodeShare(clickedNodeHandle) - if (shareData != null) { - if (!shareData.isVerified && shareData.isPending) { + val node = state().nodes.getOrNull(actualPosition)?.first + val shareData = state().nodes.getOrNull(actualPosition)?.second + when { + shareData?.isPending == true -> { showCanNotVerifyContact(shareData.user) - } else { + } + shareData?.isVerified == false -> { openAuthenticityCredentials(shareData.user) } - } else { - when { - // select mode - adapter?.isMultipleSelect == true -> { - adapter?.toggleSelection(position) - val selectedNodes = adapter?.selectedNodes - if ((selectedNodes?.size ?: 0) > 0) - updateActionModeTitle() - } + // select mode + adapter?.isMultipleSelect == true -> { + adapter?.toggleSelection(position) + val selectedNodes = adapter?.selectedNodes + if ((selectedNodes?.size ?: 0) > 0) + updateActionModeTitle() + } - // click on a folder - state().nodes[actualPosition].isFolder -> - navigateToFolder(state().nodes[actualPosition]) + // click on a folder + node?.isFolder == true -> + navigateToFolder(node) - // click on a file - else -> + // click on a file + else -> + node?.let { openFile( - state().nodes[actualPosition], + it, Constants.OUTGOING_SHARES_ADAPTER, actualPosition ) - } + } } } @@ -245,8 +246,6 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() - adapter?.setUnverifiedOutgoingShareData(it.unverifiedOutgoingShares) - adapter?.setUnverifiedOutgoingNodeHandles(it.unVerifiedOutgoingNodeHandles) updateNodes(it.nodes) setEmptyView(it.isInvalidHandle) } @@ -257,11 +256,12 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { /** * Update displayed nodes * - * @param nodes the list of nodes to display + * @param nodes the list of nodes to display with his shareData associated */ - private fun updateNodes(nodes: List) { - val mutableListNodes = ArrayList(nodes) - adapter?.setNodes(mutableListNodes) + private fun updateNodes(nodes: List>) { + val mutableListNodes = nodes.map { it.first } + val mutableListShareData = nodes.map { it.second } + adapter?.setNodesWithShareData(mutableListNodes, mutableListShareData) } /** @@ -272,7 +272,7 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { adapter = MegaNodeAdapter( requireActivity(), this, - state().nodes, + state().nodes.map { it.first }, state().outgoingHandle, recyclerView, Constants.OUTGOING_SHARES_ADAPTER, @@ -429,4 +429,4 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { requireActivity().startActivity(this) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 3f0a0a8fedb..720d946660b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -13,6 +13,7 @@ import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates import mega.privacy.android.app.presentation.shares.outgoing.model.OutgoingSharesState import mega.privacy.android.domain.entity.user.UserChanges +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle @@ -74,7 +75,6 @@ class OutgoingSharesViewModel @Inject constructor( */ fun refreshOutgoingSharesNode() = viewModelScope.launch { refreshNodes()?.let { setNodes(it) } - refreshUnverifiedOutgoingSharesNodes() } /** @@ -164,7 +164,7 @@ class OutgoingSharesViewModel @Inject constructor( * * @param nodes the list of nodes to set */ - private fun setNodes(nodes: List) { + private fun setNodes(nodes: List>) { _state.update { it.copy(nodes = nodes, isLoading = false) } } @@ -172,27 +172,36 @@ class OutgoingSharesViewModel @Inject constructor( * Refresh the list of nodes from api * * @param handle + * @return a list of Pair + * ShareData is null if the MegaNode is already verified + * A node that is shared among multiple users and not verified by them will produce + * distinct elements */ - private suspend fun refreshNodes(handle: Long = _state.value.outgoingHandle): List? { + private suspend fun refreshNodes(handle: Long = _state.value.outgoingHandle): List>? { Timber.d("refreshOutgoingSharesNodes") - return getOutgoingSharesChildrenNode(handle) - } - /** - * Refresh unverified outgoing shares - */ - private suspend fun refreshUnverifiedOutgoingSharesNodes() { - val unverifiedOutgoingShares = getUnverifiedOutgoingShares(_state.value.sortOrder) - .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - .filter { shareData -> !shareData.isVerified } - val handles = unverifiedOutgoingShares - .map { shareData -> shareData.nodeHandle } - _state.update { - it.copy( - unverifiedOutgoingShares = unverifiedOutgoingShares, - unVerifiedOutgoingNodeHandles = handles - ) + val unverifiedNodes = if (state.value.outgoingTreeDepth == 0) { + getUnverifiedOutgoingShares(_state.value.sortOrder) + .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + .filter { shareData -> !shareData.isVerified } + .mapNotNull { shareData -> + getNodeByHandle(shareData.nodeHandle)?.let { + Pair(it, shareData) + } + } + } else { + null } + + val verifiedNodes: List>? = + getOutgoingSharesChildrenNode(handle)?.map { Pair(it, null) } + + // Combine the list of unverified nodes and the list of verified nodes + // If one node is shared to multiple user, + // it will add distinct element for each user that did not verify + return unverifiedNodes?.takeIf { unverifiedNodes.isNotEmpty() }?.let { list1 -> + verifiedNodes?.let { list2 -> list1 + list2 } ?: list1 + } ?: verifiedNodes } /** @@ -207,11 +216,4 @@ class OutgoingSharesViewModel @Inject constructor( ?.let { getNodeByHandle(it) == null } ?: true } - - /** - * Find if nodehandle is in unverifiedOutgoingShares list - */ - fun getUnVerifiedOutgoingNodeShare(handle: Long?) = - state.value.unverifiedOutgoingShares.find { node -> node.nodeHandle == handle } - } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index 64b886a5841..9aec436e80b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -3,6 +3,7 @@ package mega.privacy.android.app.presentation.shares.outgoing.model import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import nz.mega.sdk.MegaNode +import nz.mega.sdk.MegaShare /** * Outgoing shares UI state @@ -10,23 +11,19 @@ import nz.mega.sdk.MegaNode * @param outgoingHandle current outgoing shares handle * @param outgoingTreeDepth current outgoing tree depth * @param outgoingParentHandle parent handle of the current outgoing node - * @param nodes current list of nodes + * @param nodes current list of nodes with his shareData associated if unverified or pending * @param isInvalidHandle true if handle is invalid * @param isLoading true if the nodes are loading * @param sortOrder current sort order - * @param unverifiedOutgoingShares List of unverified outgoing [ShareData] - * @param unVerifiedOutgoingNodeHandles List of Unverified outgoing node handles */ data class OutgoingSharesState( val outgoingHandle: Long = -1L, val outgoingTreeDepth: Int = 0, val outgoingParentHandle: Long? = null, - val nodes: List = emptyList(), + val nodes: List> = emptyList(), val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val unverifiedOutgoingShares: List = emptyList(), - val unVerifiedOutgoingNodeHandles: List = emptyList(), ) { /** @@ -35,4 +32,4 @@ data class OutgoingSharesState( * @return true if at the root of the outgoing shares page */ fun isFirstNavigationLevel() = outgoingTreeDepth == 0 -} \ No newline at end of file +} diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index 782989a55ae..8103859a22d 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -14,10 +14,7 @@ import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel -import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder -import mega.privacy.android.domain.entity.node.Node -import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate import mega.privacy.android.domain.entity.user.UserUpdate import mega.privacy.android.domain.usecase.GetCloudSortOrder @@ -55,8 +52,7 @@ class OutgoingSharesViewModelTest { var instantExecutorRule = InstantTaskExecutorRule() private val getUnverifiedOutgoingShares = mock { - val shareData = ShareData("user", 8766L, 0, 987654678L, true, false) - onBlocking { invoke(any()) }.thenReturn(listOf(shareData)) + onBlocking { invoke(any()) }.thenReturn(emptyList()) } @Before @@ -212,7 +208,7 @@ class OutgoingSharesViewModelTest { @Test fun `test that is invalid handle is set to false when call set outgoing tree depth with valid handle`() = runTest { - whenever(getOutgoingSharesChildrenNode(any())).thenReturn(mock()) + whenever(getOutgoingSharesChildrenNode(any())).thenReturn(listOf()) whenever(getNodeByHandle(any())).thenReturn(mock()) underTest.state.map { it.isInvalidHandle }.distinctUntilChanged() @@ -226,7 +222,7 @@ class OutgoingSharesViewModelTest { @Test fun `test that is invalid handle is set to true when call set outgoing tree depth with invalid handle`() = runTest { - whenever(getOutgoingSharesChildrenNode(any())).thenReturn(mock()) + whenever(getOutgoingSharesChildrenNode(any())).thenReturn(listOf()) whenever(getNodeByHandle(any())).thenReturn(mock()) underTest.state.map { it.isInvalidHandle }.distinctUntilChanged() @@ -242,7 +238,7 @@ class OutgoingSharesViewModelTest { @Test fun `test that is invalid handle is set to true when cannot retrieve node`() = runTest { - whenever(getOutgoingSharesChildrenNode(any())).thenReturn(mock()) + whenever(getOutgoingSharesChildrenNode(any())).thenReturn(listOf()) whenever(getNodeByHandle(any())).thenReturn(mock()) underTest.state.map { it.isInvalidHandle }.distinctUntilChanged() @@ -302,9 +298,9 @@ class OutgoingSharesViewModelTest { runTest { val node1 = mock() val node2 = mock() - val expected = listOf(node1, node2) + val expected = listOf(Pair(node1, null), Pair(node2, null)) - whenever(getOutgoingSharesChildrenNode(any())).thenReturn(expected) + whenever(getOutgoingSharesChildrenNode(any())).thenReturn(expected.map { it.first }) underTest.state.map { it.nodes }.distinctUntilChanged() .test { @@ -319,9 +315,9 @@ class OutgoingSharesViewModelTest { runTest { val node1 = mock() val node2 = mock() - val expected = listOf(node1, node2) + val expected = listOf(Pair(node1, null), Pair(node2, null)) - whenever(getOutgoingSharesChildrenNode(123456789L)).thenReturn(expected) + whenever(getOutgoingSharesChildrenNode(123456789L)).thenReturn(expected.map { it.first }) whenever(getOutgoingSharesChildrenNode(987654321L)).thenReturn(null) underTest.state.map { it.nodes }.distinctUntilChanged() @@ -379,9 +375,6 @@ class OutgoingSharesViewModelTest { @Test fun `test that refresh nodes is called when receiving a node update`() = runTest { - val node = mock { - on { this.id }.thenReturn(NodeId(987654321L)) - } monitorNodeUpdates.emit(NodeUpdate(emptyMap())) // initialization call + receiving a node update call verify( @@ -437,16 +430,4 @@ class OutgoingSharesViewModelTest { assertThat(awaitItem()).isEqualTo(expected) } } - - @Test - fun `test that unverified outgoing shares are returned`() = runTest { - val node1 = mock() - whenever(getNodeByHandle(any())).thenReturn(node1) - assertThat(getNodeByHandle(any())).isNotNull() - initViewModel() - underTest.state.map { it.unverifiedOutgoingShares }.distinctUntilChanged() - .test { - assertThat(awaitItem().size).isEqualTo(1) - } - } } From 88d296cbdcee4e83ec167374214f97f1d4fa02ad Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Mon, 6 Mar 2023 00:07:59 +1300 Subject: [PATCH 222/334] AND-15249: Mandatory fingerprint authentication (FIX) - Outgoing --- .../android/app/main/ManagerActivity.java | 2 +- .../app/main/adapters/MegaNodeAdapter.java | 14 +--- .../NodeOptionsBottomSheetDialogFragment.java | 23 +------ .../shares/incoming/IncomingSharesFragment.kt | 69 ++++++++++--------- .../incoming/IncomingSharesViewModel.kt | 46 ++++++++----- .../incoming/model/IncomingSharesState.kt | 6 +- .../incoming/IncomingSharesViewModelTest.kt | 38 +++------- 7 files changed, 78 insertions(+), 120 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index c758a9d72b4..8dd25170a2c 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -2510,7 +2510,7 @@ private void collectFlows() { }); ViewExtensionsKt.collectFlow(this, incomingSharesViewModel.getState(), Lifecycle.State.STARTED, incomingSharesState -> { - addUnverifiedIncomingCountBadge(incomingSharesState.getUnverifiedIncomingShares().size()); + addUnverifiedIncomingCountBadge(incomingSharesState.getNodes().stream().filter(pair -> pair.getSecond() != null).collect(Collectors.toList()).size()); return Unit.INSTANCE; }); diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index 5607f569bf6..d34202b96c8 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -158,7 +158,6 @@ public class MegaNodeAdapter extends RecyclerView.Adapter unverifiedIncomingNodeHandles = new HashSet<>(); public static class ViewHolderBrowser extends RecyclerView.ViewHolder { @@ -1120,8 +1119,7 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { } else { holder.permissionsIcon.setImageResource(R.drawable.ic_shared_read); } - boolean hasUnverifiedNodes = !unverifiedIncomingNodeHandles.isEmpty() - && unverifiedIncomingNodeHandles.contains(node.getHandle()); + boolean hasUnverifiedNodes = shareData != null && shareData.get(position) != null; if (hasUnverifiedNodes) { showUnverifiedNodeUi(holder, true, node, null); } @@ -1556,16 +1554,6 @@ public void onCancelClicked() { unHandledItem = -1; } - /** - * Adds unverified incoming nodes to Set - * - * @param handles - List of incoming node handles - */ - public void setUnverifiedIncomingNodeHandles(List handles) { - unverifiedIncomingNodeHandles.clear(); - unverifiedIncomingNodeHandles.addAll(handles); - } - /** * Function to show Unverified node UI items accordingly * diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java index a87f0717b63..87dfa51e1f3 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/NodeOptionsBottomSheetDialogFragment.java @@ -81,7 +81,6 @@ import java.util.List; import java.util.stream.Collectors; -import kotlin.Pair; import kotlin.Unit; import mega.privacy.android.app.MegaOffline; import mega.privacy.android.app.MimeTypeList; @@ -99,9 +98,6 @@ import mega.privacy.android.app.presentation.fileinfo.FileInfoActivity; import mega.privacy.android.app.presentation.manager.model.SharesTab; import mega.privacy.android.app.presentation.search.SearchViewModel; -import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel; -import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel; -import mega.privacy.android.app.presentation.shares.outgoing.model.OutgoingSharesState; import mega.privacy.android.app.utils.AlertDialogUtil; import mega.privacy.android.app.utils.Constants; import mega.privacy.android.app.utils.MegaNodeUtil; @@ -181,8 +177,6 @@ public class NodeOptionsBottomSheetDialogFragment extends BaseBottomSheetDialogF private AlertDialog cannotOpenFileDialog; private MegaUser user; - - private IncomingSharesViewModel incomingSharesViewModel; private NodeOptionsBottomSheetViewModel nodeOptionsBottomSheetViewModel; public NodeOptionsBottomSheetDialogFragment(int mode) { @@ -199,7 +193,6 @@ public NodeOptionsBottomSheetDialogFragment() { public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class); - incomingSharesViewModel = new ViewModelProvider(requireActivity()).get(IncomingSharesViewModel.class); nodeOptionsBottomSheetViewModel = new ViewModelProvider(this).get(NodeOptionsBottomSheetViewModel.class); } @@ -737,16 +730,6 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat } super.onViewCreated(view, savedInstanceState); - if(nC.nodeComesFromIncoming(node)) { - ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), incomingSharesViewModel.getState(), Lifecycle.State.STARTED, state -> { - if (mMode == SHARED_ITEMS_MODE - && isNodeUnverified(state.getUnVerifiedIncomingNodeHandles())) { - setUnverifiedNodeUserName(state.getUnverifiedIncomingShares()); - hideNodeActions(shareData); - } - return Unit.INSTANCE; - }); - } ViewExtensionsKt.collectFlow(getViewLifecycleOwner(), nodeOptionsBottomSheetViewModel.getState(), Lifecycle.State.STARTED, state -> { if(state.isOpenShareDialogSuccess() != null) { @@ -954,11 +937,7 @@ private void hideNodeActions(ShareData shareData) { contentView.findViewById(R.id.share_option).setVisibility(View.GONE); contentView.findViewById(R.id.clear_share_option).setVisibility(View.GONE); } - - private boolean isNodeUnverified(List shareDataList) { - return shareDataList.contains(node.getHandle()); - } - + @SuppressLint("NonConstantResourceId") @Override public void onClick(View v) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 4d01ab45820..d60c3bc558d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -34,6 +34,7 @@ import mega.privacy.android.app.utils.MegaNodeUtil.allHaveFullAccess import mega.privacy.android.app.utils.MegaNodeUtil.areAllFileNodesAndNotTakenDown import mega.privacy.android.app.utils.MegaNodeUtil.areAllNotTakenDown import mega.privacy.android.app.utils.Util +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import nz.mega.sdk.MegaError import nz.mega.sdk.MegaNode @@ -100,38 +101,44 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { override fun itemClick(position: Int) { val actualPosition = position - 1 - if (state().unVerifiedIncomingNodeHandles.contains(state().nodes[actualPosition].handle)) { - Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { - putExtra(Constants.IS_NODE_INCOMING, - nodeController.nodeComesFromIncoming(state().nodes[actualPosition])) - putExtra(Constants.EMAIL, - ContactUtil.getContactEmailDB(state().nodes[actualPosition].owner)) - requireActivity().startActivity(this) - } - } else { - when { - // select mode - adapter?.isMultipleSelect == true -> { - adapter?.toggleSelection(position) - val selectedNodes = adapter?.selectedNodes - if ((selectedNodes?.size ?: 0) > 0) - updateActionModeTitle() + val node = state().nodes.getOrNull(actualPosition)?.first + val shareData = state().nodes.getOrNull(actualPosition)?.second + when { + shareData?.isVerified == false -> { + Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { + putExtra( + Constants.IS_NODE_INCOMING, + nodeController.nodeComesFromIncoming(node) + ) + putExtra( + Constants.EMAIL, + node?.let { ContactUtil.getContactEmailDB(it.owner) } + ) + requireActivity().startActivity(this) } + } + // select mode + adapter?.isMultipleSelect == true -> { + adapter?.toggleSelection(position) + val selectedNodes = adapter?.selectedNodes + if ((selectedNodes?.size ?: 0) > 0) + updateActionModeTitle() + } - // click on a folder - state().nodes[actualPosition].isFolder -> - navigateToFolder(state().nodes[actualPosition]) + // click on a folder + node?.isFolder == true -> + navigateToFolder(node) - // click on a file - else -> + // click on a file + else -> + node?.let { openFile( - state().nodes[actualPosition], + it, Constants.INCOMING_SHARES_ADAPTER, actualPosition ) - } + } } - } override fun navigateToFolder(node: MegaNode) { @@ -254,7 +261,6 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { visibilityFastScroller() hideActionMode() - adapter?.setUnverifiedIncomingNodeHandles(it.unVerifiedIncomingNodeHandles) updateNodes(it.nodes) setEmptyView(it.isInvalidHandle) } @@ -267,9 +273,10 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { * * @param nodes the list of nodes to display */ - private fun updateNodes(nodes: List) { - val mutableListNodes = ArrayList(nodes) - adapter?.setNodes(mutableListNodes) + private fun updateNodes(nodes: List>) { + val mutableListNodes = nodes.map { it.first } + val mutableListShareData = nodes.map { it.second } + adapter?.setNodesWithShareData(mutableListNodes, mutableListShareData) } /** @@ -280,7 +287,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { adapter = MegaNodeAdapter( requireActivity(), this, - state().nodes, + state().nodes.map { it.first }, state().incomingHandle, recyclerView, Constants.INCOMING_SHARES_ADAPTER, @@ -313,7 +320,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { * If user navigates from notification about new nodes added to shared folder select all nodes and scroll to the first node in the list */ private fun selectNewlyAddedNodes() { - val positions = managerActivity?.getPositionsList(state().nodes) + val positions = managerActivity?.getPositionsList(state().nodes.map { it.first }) if (!positions.isNullOrEmpty()) { val firstPosition = Collections.min(positions) activateActionMode() @@ -410,4 +417,4 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { return true } } -} \ No newline at end of file +} diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index 4cdda538230..eb432d3ee28 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -14,6 +14,7 @@ import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates import mega.privacy.android.app.presentation.shares.incoming.model.IncomingSharesState import mega.privacy.android.domain.entity.user.UserChanges +import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle @@ -178,7 +179,7 @@ class IncomingSharesViewModel @Inject constructor( * * @param nodes the list of nodes to set */ - private fun setNodes(nodes: List) { + private fun setNodes(nodes: List>) { _state.update { it.copy(nodes = nodes, isLoading = false) } } @@ -187,27 +188,34 @@ class IncomingSharesViewModel @Inject constructor( * * @param handle */ - private suspend fun refreshNodes(handle: Long = _state.value.incomingHandle): List? { + private suspend fun refreshNodes(handle: Long = _state.value.incomingHandle): List>? { Timber.d("refreshIncomingSharesNodes") - val unverifiedIncomingShares = getUnverifiedIncomingShares(_state.value.sortOrder) - .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } - val handles = unverifiedIncomingShares.map { shareData -> shareData.nodeHandle } - _state.update { - it.copy( - unverifiedIncomingShares = unverifiedIncomingShares, - unVerifiedIncomingNodeHandles = handles - ) + + val unverifiedNodes = if (state.value.incomingTreeDepth == 0) { + getUnverifiedIncomingShares(_state.value.sortOrder) + .filter { shareData -> !isInvalidHandle(shareData.nodeHandle) } + .mapNotNull { shareData -> + getNodeByHandle(shareData.nodeHandle)?.let { + Pair(it, shareData) + } + } + } else { + null } - val unverifiedIncomingSharesNodes = handles.mapNotNull { getNodeByHandle(it) } - val incomingSharesNodes = getIncomingSharesChildrenNode(handle) - return if ( - unverifiedIncomingSharesNodes.isNotEmpty() || incomingSharesNodes.isNullOrEmpty().not() - ) - mutableListOf().apply { - addAll(unverifiedIncomingSharesNodes) - incomingSharesNodes?.let { addAll(it) } - }.distinctBy { it.handle } else null + // We need to filter out the nodes that are in the unverified list + // Somehow when a node is un-decrypted, it will be present in the result + // of getUnverifiedIncomingShares and getIncomingSharesChildrenNode + val verifiedNodes: List>? = + getIncomingSharesChildrenNode(handle) + ?.filter { it -> + !(unverifiedNodes?.map { it.first.handle }?.contains(it.handle) ?: false) + } + ?.map { Pair(it, null) } + + return unverifiedNodes?.takeIf { unverifiedNodes.isNotEmpty() }?.let { list1 -> + verifiedNodes?.let { list2 -> list1 + list2 } ?: list1 + } ?: verifiedNodes } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index 4169301854d..25e7f2c3d7a 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -21,12 +21,10 @@ data class IncomingSharesState( val incomingHandle: Long = -1L, val incomingTreeDepth: Int = 0, val incomingParentHandle: Long? = null, - val nodes: List = emptyList(), + val nodes: List> = emptyList(), val isInvalidHandle: Boolean = true, val isLoading: Boolean = false, val sortOrder: SortOrder = SortOrder.ORDER_NONE, - val unverifiedIncomingShares: List = emptyList(), - val unVerifiedIncomingNodeHandles: List = emptyList(), ) { /** @@ -35,4 +33,4 @@ data class IncomingSharesState( * @return true if at the root of the incoming shares page */ fun isFirstNavigationLevel() = incomingTreeDepth == 0 -} \ No newline at end of file +} diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index 4ba386d311e..5293ed792ad 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -16,7 +16,6 @@ import mega.privacy.android.app.domain.usecase.AuthorizeNode import mega.privacy.android.app.domain.usecase.GetIncomingSharesChildrenNode import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.presentation.shares.incoming.IncomingSharesViewModel -import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeChanges @@ -60,7 +59,7 @@ class IncomingSharesViewModelTest { var instantExecutorRule = InstantTaskExecutorRule() private val getUnverifiedIncomingShares = mock { - onBlocking { invoke(any()) }.thenReturn(listOf()) + onBlocking { invoke(any()) }.thenReturn(emptyList()) } @Before @@ -343,12 +342,12 @@ class IncomingSharesViewModelTest { val node1 = mock { on { this.handle }.thenReturn(1234L) } - val node2 = mock() { + val node2 = mock { on { this.handle }.thenReturn(5678L) } - val expected = listOf(node1, node2) + val expected = listOf(Pair(node1, null), Pair(node2, null)) - whenever(getIncomingSharesChildrenNode(any())).thenReturn(expected) + whenever(getIncomingSharesChildrenNode(any())).thenReturn(expected.map { it.first }) underTest.state.map { it.nodes }.distinctUntilChanged() .test { @@ -364,12 +363,12 @@ class IncomingSharesViewModelTest { val node1 = mock { on { this.handle }.thenReturn(1234L) } - val node2 = mock() { + val node2 = mock { on { this.handle }.thenReturn(5678L) } - val expected = listOf(node1, node2) + val expected = listOf(Pair(node1, null), Pair(node2, null)) - whenever(getIncomingSharesChildrenNode(123456789L)).thenReturn(expected) + whenever(getIncomingSharesChildrenNode(123456789L)).thenReturn(expected.map { it.first }) whenever(getIncomingSharesChildrenNode(987654321L)).thenReturn(null) underTest.state.map { it.nodes }.distinctUntilChanged() @@ -411,7 +410,7 @@ class IncomingSharesViewModelTest { fun `test that parent handle is set to null when refreshNodes fails`() = runTest { whenever(getParentNodeHandle(any())).thenReturn(111111111L) - whenever(getIncomingSharesChildrenNode(any())).thenReturn(mock()) + whenever(getIncomingSharesChildrenNode(any())).thenReturn(listOf()) whenever(getNodeByHandle(any())).thenReturn(mock()) underTest.state.map { it.incomingParentHandle }.distinctUntilChanged() @@ -500,10 +499,6 @@ class IncomingSharesViewModelTest { fun `test that if monitor node update does not returns the current node, do not redirect to root of incoming shares`() = runTest { val handle = 123456789L - val node = mock { - on { this.id }.thenReturn(NodeId(987654321L)) - on { this.isIncomingShare }.thenReturn(true) - } whenever(getIncomingSharesChildrenNode(any())).thenReturn(mock()) underTest.state.map { it.incomingHandle }.distinctUntilChanged() @@ -517,9 +512,6 @@ class IncomingSharesViewModelTest { @Test fun `test that refresh nodes is called when receiving a node update`() = runTest { - val node = mock { - on { this.id }.thenReturn(NodeId(987654321L)) - } monitorNodeUpdates.emit(NodeUpdate(emptyMap())) // initialization call + receiving a node update call verify( @@ -527,18 +519,4 @@ class IncomingSharesViewModelTest { times(2) ).invoke(underTest.state.value.incomingHandle) } - - @Test - fun `test that unverified incoming shares are returned`() = runTest { - val node1 = mock() - whenever(getNodeByHandle(any())).thenReturn(node1) - assertThat(getNodeByHandle(any())).isNotNull() - val expected = ShareData("user", 8766L, 0, 987654678L, true, false) - whenever(getUnverifiedIncomingShares(any())).thenReturn(listOf(expected)) - initViewModel() - underTest.state.map { it.unverifiedIncomingShares }.distinctUntilChanged() - .test { - assertThat(awaitItem().size).isEqualTo(1) - } - } } From d860ca1fa0ffcd10c9298bcb32a936e0c5b98ffd Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Mon, 6 Mar 2023 18:22:15 +1300 Subject: [PATCH 223/334] Pre-Release v7.6 --- sdk/src/main/jni/mega/sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index e9fdc26cb72..778f83d7ed3 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit e9fdc26cb7290ffa6d661d14d387d47e77a56c79 +Subproject commit 778f83d7ed3780c536da5f05dc1205cffe5917e7 From 69e331eea16338ef0ca13f5c407a21c1d4eb2fd8 Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Mon, 6 Mar 2023 21:36:11 +1300 Subject: [PATCH 224/334] AND-15249: Mandatory fingerprint authentication (FIX) - Security Upgrade --- .../android/app/listeners/GlobalListener.kt | 73 +++++++++++++------ .../android/app/main/ManagerActivity.java | 14 ---- .../SecurityUpgradeDialogFragment.kt | 34 ++++++--- .../SecurityUpgradeViewModel.kt | 34 ++++++++- .../model/SecurityUpgradeState.kt | 10 +++ .../presentation/manager/ManagerViewModel.kt | 30 +++----- .../SecurityUpgradeViewModelTest.kt | 6 +- .../manager/ManagerViewModelTest.kt | 2 + .../android/data/facade/AppEventFacade.kt | 10 +++ .../android/data/gateway/AppEventGateway.kt | 11 +++ .../data/repository/NodeRepositoryImpl.kt | 9 +++ .../data/repository/NodeRepositoryImplTest.kt | 7 +- .../domain/di/InternalFileNodeModule.kt | 13 +++- .../domain/repository/NodeRepository.kt | 14 ++++ .../filenode/MonitorSecurityUpgrade.kt | 10 +++ .../usecase/filenode/SetSecurityUpgrade.kt | 14 ++++ 16 files changed, 215 insertions(+), 76 deletions(-) create mode 100644 app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/model/SecurityUpgradeState.kt create mode 100644 domain/src/main/kotlin/mega/privacy/android/domain/usecase/filenode/MonitorSecurityUpgrade.kt create mode 100644 domain/src/main/kotlin/mega/privacy/android/domain/usecase/filenode/SetSecurityUpgrade.kt diff --git a/app/src/main/java/mega/privacy/android/app/listeners/GlobalListener.kt b/app/src/main/java/mega/privacy/android/app/listeners/GlobalListener.kt index 35e2d84fdf5..263f4a3c36c 100644 --- a/app/src/main/java/mega/privacy/android/app/listeners/GlobalListener.kt +++ b/app/src/main/java/mega/privacy/android/app/listeners/GlobalListener.kt @@ -27,11 +27,11 @@ import mega.privacy.android.app.fcm.ContactsAdvancedNotificationBuilder import mega.privacy.android.app.fragments.settingsFragments.cookie.data.CookieType import mega.privacy.android.app.fragments.settingsFragments.cookie.usecase.GetCookieSettingsUseCase import mega.privacy.android.app.globalmanagement.MegaChatNotificationHandler -import mega.privacy.android.app.presentation.login.LoginActivity -import mega.privacy.android.app.presentation.login.LoginActivity.Companion.ACTION_FORCE_RELOAD_ACCOUNT import mega.privacy.android.app.main.ManagerActivity import mega.privacy.android.app.middlelayer.reporter.CrashReporter import mega.privacy.android.app.middlelayer.reporter.PerformanceReporter +import mega.privacy.android.app.presentation.login.LoginActivity +import mega.privacy.android.app.presentation.login.LoginActivity.Companion.ACTION_FORCE_RELOAD_ACCOUNT import mega.privacy.android.app.service.iar.RatingHandlerImpl import mega.privacy.android.app.utils.AlertsAndWarnings.showOverDiskQuotaPaywallWarning import mega.privacy.android.app.utils.Constants @@ -46,6 +46,7 @@ import mega.privacy.android.domain.usecase.GetAccountDetails import mega.privacy.android.domain.usecase.GetNumberOfSubscription import mega.privacy.android.domain.usecase.GetPaymentMethod import mega.privacy.android.domain.usecase.GetPricing +import mega.privacy.android.domain.usecase.filenode.SetSecurityUpgrade import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaApiJava import nz.mega.sdk.MegaContactRequest @@ -74,6 +75,7 @@ class GlobalListener @Inject constructor( private val getPaymentMethod: GetPaymentMethod, private val getPricing: GetPricing, private val getNumberOfSubscription: GetNumberOfSubscription, + private val setSecurityUpgrade: SetSecurityUpgrade, ) : MegaGlobalListenerInterface { override fun onUsersUpdate(api: MegaApiJava, users: ArrayList?) { @@ -95,8 +97,10 @@ class GlobalListener @Inject constructor( if (user.hasChanged(MegaUser.CHANGE_TYPE_CAMERA_UPLOADS_FOLDER) && isMyChange) { //user has change CU attribute, need to update local ones Timber.d("Get CameraUpload attribute when change on other client.") - api.getUserAttribute(MegaApiJava.USER_ATTR_CAMERA_UPLOADS_FOLDER, - GetCameraUploadAttributeListener(appContext)) + api.getUserAttribute( + MegaApiJava.USER_ATTR_CAMERA_UPLOADS_FOLDER, + GetCameraUploadAttributeListener(appContext) + ) return@forEach } if (user.hasChanged(MegaUser.CHANGE_TYPE_RICH_PREVIEWS) && isMyChange) { @@ -125,7 +129,8 @@ class GlobalListener @Inject constructor( private fun notifyNotificationCountChange(api: MegaApiJava) { val incomingContactRequests = api.incomingContactRequests LiveEventBus.get(Constants.EVENT_NOTIFICATION_COUNT_CHANGE, Int::class.java).post( - api.numUnreadUserAlerts + (incomingContactRequests?.size ?: 0)) + api.numUnreadUserAlerts + (incomingContactRequests?.size ?: 0) + ) } override fun onNodesUpdate(api: MegaApiJava, nodeList: ArrayList?) { @@ -169,23 +174,29 @@ class GlobalListener @Inject constructor( val notificationBuilder: ContactsAdvancedNotificationBuilder = ContactsAdvancedNotificationBuilder.newInstance( appContext, - megaApi) + megaApi + ) notificationBuilder.removeAllIncomingContactNotifications() notificationBuilder.showIncomingContactRequestNotification() - Timber.d("IPC: %s cr.isOutgoing: %s cr.getStatus: %d", + Timber.d( + "IPC: %s cr.isOutgoing: %s cr.getStatus: %d", cr.sourceEmail, cr.isOutgoing, - cr.status) + cr.status + ) } else if (cr.status == MegaContactRequest.STATUS_ACCEPTED && cr.isOutgoing) { val notificationBuilder: ContactsAdvancedNotificationBuilder = ContactsAdvancedNotificationBuilder.newInstance( appContext, - megaApi) + megaApi + ) notificationBuilder.showAcceptanceContactRequestNotification(cr.targetEmail) - Timber.d("ACCEPT OPR: %s cr.isOutgoing: %s cr.getStatus: %d", + Timber.d( + "ACCEPT OPR: %s cr.isOutgoing: %s cr.getStatus: %d", cr.sourceEmail, cr.isOutgoing, - cr.status) + cr.status + ) RatingHandlerImpl(appContext).showRatingBaseOnContacts() } if (cr.status == MegaContactRequest.STATUS_ACCEPTED) { @@ -220,13 +231,18 @@ class GlobalListener @Inject constructor( } MegaEvent.EVENT_ACCOUNT_BLOCKED -> { Timber.d("EVENT_ACCOUNT_BLOCKED: %s", event.number) - appContext.sendBroadcast(Intent(BroadcastConstants.BROADCAST_ACTION_INTENT_EVENT_ACCOUNT_BLOCKED) - .putExtra(BroadcastConstants.EVENT_NUMBER, event.number) - .putExtra(BroadcastConstants.EVENT_TEXT, event.text)) + appContext.sendBroadcast( + Intent(BroadcastConstants.BROADCAST_ACTION_INTENT_EVENT_ACCOUNT_BLOCKED) + .putExtra(BroadcastConstants.EVENT_NUMBER, event.number) + .putExtra(BroadcastConstants.EVENT_TEXT, event.text) + ) } MegaEvent.EVENT_BUSINESS_STATUS -> sendBroadcastUpdateAccountDetails() MegaEvent.EVENT_MISC_FLAGS_READY -> checkEnabledCookies() MegaEvent.EVENT_RELOADING -> showLoginFetchingNodes() + MegaEvent.EVENT_UPGRADE_SECURITY -> applicationScope.launch { + setSecurityUpgrade(true) + } } } @@ -242,8 +258,10 @@ class GlobalListener @Inject constructor( } private fun sendBroadcastUpdateAccountDetails() { - appContext.sendBroadcast(Intent(Constants.BROADCAST_ACTION_INTENT_UPDATE_ACCOUNT_DETAILS) - .putExtra(BroadcastConstants.ACTION_TYPE, Constants.UPDATE_ACCOUNT_DETAILS)) + appContext.sendBroadcast( + Intent(Constants.BROADCAST_ACTION_INTENT_UPDATE_ACCOUNT_DETAILS) + .putExtra(BroadcastConstants.ACTION_TYPE, Constants.UPDATE_ACCOUNT_DETAILS) + ) } private fun showSharedFolderNotification(n: MegaNode) { @@ -260,22 +278,27 @@ class GlobalListener @Inject constructor( } val source = "" + n.name + " " + appContext.getString(R.string.incoming_folder_notification) + " " + Util.toCDATA( - name) + name + ) val notificationContent = HtmlCompat.fromHtml(source, HtmlCompat.FROM_HTML_MODE_LEGACY) val notificationChannelId = Constants.NOTIFICATION_CHANNEL_CLOUDDRIVE_ID val intent: Intent = Intent(appContext, ManagerActivity::class.java) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) .setAction(Constants.ACTION_INCOMING_SHARED_FOLDER_NOTIFICATION) - val pendingIntent = PendingIntent.getActivity(appContext, 0, intent, - PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE) + val pendingIntent = PendingIntent.getActivity( + appContext, 0, intent, + PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE + ) val notificationTitle: String = appContext.getString(if (n.hasChanged(MegaNode.CHANGE_TYPE_NEW)) R.string.title_incoming_folder_notification else R.string.context_permissions_changed) val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val channel = NotificationChannel(notificationChannelId, + val channel = NotificationChannel( + notificationChannelId, Constants.NOTIFICATION_CHANNEL_CLOUDDRIVE_NAME, - NotificationManager.IMPORTANCE_HIGH) + NotificationManager.IMPORTANCE_HIGH + ) channel.setShowBadge(true) notificationManager.createNotificationChannel(channel) } @@ -293,8 +316,10 @@ class GlobalListener @Inject constructor( .setColor(ContextCompat.getColor(appContext, R.color.red_600_red_300)) .setLargeIcon((d as BitmapDrawable).bitmap) .setPriority(NotificationManager.IMPORTANCE_HIGH) - notificationManager.notify(Constants.NOTIFICATION_PUSH_CLOUD_DRIVE, - notificationBuilder.build()) + notificationManager.notify( + Constants.NOTIFICATION_PUSH_CLOUD_DRIVE, + notificationBuilder.build() + ) } catch (e: Exception) { Timber.e(e) } @@ -335,4 +360,4 @@ class GlobalListener @Inject constructor( getAccountDetails(forceRefresh = true) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java index 8dd25170a2c..94e75850831 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.java @@ -845,8 +845,6 @@ public void onChanged(Boolean aBoolean) { public MegaNode viewInFolderNode; - private ArrayList outgoingFolderNames = new ArrayList<>(); - /** * Broadcast to update the completed transfers tab. */ @@ -1288,7 +1286,6 @@ protected void onCreate(Bundle savedInstanceState) { rubbishBinViewModel = new ViewModelProvider(this).get(RubbishBinViewModel.class); searchViewModel = new ViewModelProvider(this).get(SearchViewModel.class); userInfoViewModel = new ViewModelProvider(this).get(UserInfoViewModel.class); - viewModel.monitorGlobalEventUpgradeForUpgradeSecurity(); viewModel.getUpdateUserAlerts().observe(this, new EventObserver<>(userAlerts -> { updateUserAlerts(userAlerts); @@ -2449,21 +2446,10 @@ private void collectFlows() { ViewExtensionsKt.collectFlow(this, viewModel.getState(), Lifecycle.State.STARTED, managerState -> { if (managerState.getShouldAlertUserAboutSecurityUpgrade()) { - ViewExtensionsKt.collectFlow(this, outgoingSharesViewModel.getState(), Lifecycle.State.STARTED, outgoingSharesState -> { - outgoingFolderNames.clear(); - for (int i = 0; i < outgoingSharesState.getNodes().size(); i++) { - outgoingFolderNames.add(outgoingSharesState.getNodes().get(i).getFirst().getName()); - } SecurityUpgradeDialogFragment dialog = SecurityUpgradeDialogFragment.Companion.newInstance(); - Bundle bundle = new Bundle(); - bundle.putStringArrayList("nodeNames", outgoingFolderNames); - dialog.setArguments(bundle); dialog.show(getSupportFragmentManager(), SecurityUpgradeDialogFragment.TAG); viewModel.setShouldAlertUserAboutSecurityUpgrade(false); - - return Unit.INSTANCE; - }); } updateInboxSectionVisibility(managerState.getHasInboxChildren()); diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt index a54b6f569df..99c5259c867 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeDialogFragment.kt @@ -5,11 +5,14 @@ import android.os.Bundle import androidx.compose.runtime.getValue import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.DialogFragment +import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import collectAsStateWithLifecycle import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint +import mega.privacy.android.app.arch.extensions.collectFlow import mega.privacy.android.app.presentation.extensions.isDarkMode +import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel import mega.privacy.android.core.ui.theme.AndroidTheme import mega.privacy.android.domain.entity.ThemeMode import mega.privacy.android.domain.usecase.GetThemeMode @@ -22,6 +25,7 @@ import javax.inject.Inject class SecurityUpgradeDialogFragment : DialogFragment() { private val securityUpgradeViewModel by viewModels() + private val outgoingSharesViewModel: OutgoingSharesViewModel by activityViewModels() /** * Current theme @@ -29,24 +33,34 @@ class SecurityUpgradeDialogFragment : DialogFragment() { @Inject lateinit var getThemeMode: GetThemeMode - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog = - MaterialAlertDialogBuilder(requireContext()).setView( + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + collectFlow(securityUpgradeViewModel.state) { + if (it.shouldFinishScreen) { + dismiss() + } + } + + return MaterialAlertDialogBuilder(requireContext()).setView( ComposeView(requireContext()).apply { setContent { - val nodeName = arguments?.getStringArrayList("nodeNames") as List val mode by getThemeMode() .collectAsStateWithLifecycle(initialValue = ThemeMode.System) + + val state by outgoingSharesViewModel.state.collectAsStateWithLifecycle() AndroidTheme(isDark = mode.isDarkMode()) { - SecurityUpgradeDialogView(folderNames = nodeName, onCancelClick = { - requireActivity().finishAffinity() - }, onOkClick = { - securityUpgradeViewModel.upgradeAccountSecurity() - dismiss() - }) + SecurityUpgradeDialogView( + folderNames = state.nodes.map { it.first.name }, + onCancelClick = { + requireActivity().finishAffinity() + }, + onOkClick = { + securityUpgradeViewModel.upgradeAccountSecurity() + }) } } } ).create() + } companion object { /** @@ -61,4 +75,4 @@ class SecurityUpgradeDialogFragment : DialogFragment() { */ fun newInstance() = SecurityUpgradeDialogFragment() } -} \ No newline at end of file +} diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModel.kt index 37511a045c0..c57df72fe12 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModel.kt @@ -3,8 +3,13 @@ package mega.privacy.android.app.presentation.fingerprintauth import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import mega.privacy.android.app.presentation.fingerprintauth.model.SecurityUpgradeState import mega.privacy.android.domain.usecase.UpgradeSecurity +import mega.privacy.android.domain.usecase.filenode.SetSecurityUpgrade import javax.inject.Inject /** @@ -13,14 +18,39 @@ import javax.inject.Inject @HiltViewModel class SecurityUpgradeViewModel @Inject constructor( private val upgradeSecurity: UpgradeSecurity, + private val setSecurityUpgrade: SetSecurityUpgrade, ) : ViewModel() { + /** private UI state */ + private val _state = MutableStateFlow(SecurityUpgradeState()) + + /** public UI state */ + val state: StateFlow = _state + /** * Function to upgrade account security */ fun upgradeAccountSecurity() { viewModelScope.launch { - upgradeSecurity() + val result = runCatching { + upgradeSecurity() + }.onSuccess { + setSecurityUpgrade(false) + }.onFailure { + setSecurityUpgrade(true) + } + setShouldFinishScreen(result.isSuccess) + } + } + + /** + * Set UI state should finishScreen + * + * @param shouldFinish true if the screen should finish + */ + private fun setShouldFinishScreen(shouldFinish: Boolean) { + _state.update { + it.copy(shouldFinishScreen = shouldFinish) } } -} \ No newline at end of file +} diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/model/SecurityUpgradeState.kt b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/model/SecurityUpgradeState.kt new file mode 100644 index 00000000000..9326dd495f3 --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/presentation/fingerprintauth/model/SecurityUpgradeState.kt @@ -0,0 +1,10 @@ +package mega.privacy.android.app.presentation.fingerprintauth.model + +/** + * UI State for SecurityUpgradeDialogFragment + * + * @param shouldFinishScreen true if the screen should finish + */ +data class SecurityUpgradeState( + val shouldFinishScreen: Boolean = false, +) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt index f548ca3592c..35b4e55a832 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/ManagerViewModel.kt @@ -59,8 +59,8 @@ import mega.privacy.android.domain.usecase.MonitorMyAvatarFile import mega.privacy.android.domain.usecase.MonitorStorageStateEvent import mega.privacy.android.domain.usecase.SendStatisticsMediaDiscovery import mega.privacy.android.domain.usecase.billing.GetActiveSubscription +import mega.privacy.android.domain.usecase.filenode.MonitorSecurityUpgrade import mega.privacy.android.domain.usecase.viewtype.MonitorViewType -import nz.mega.sdk.MegaEvent import nz.mega.sdk.MegaNode import nz.mega.sdk.MegaUserAlert import timber.log.Timber @@ -116,6 +116,7 @@ class ManagerViewModel @Inject constructor( private val getFeatureFlagValue: GetFeatureFlagValue, private val getUnverifiedIncomingShares: GetUnverifiedIncomingShares, private val getUnverifiedOutgoingShares: GetUnverifiedOutgoingShares, + private val monitorSecurityUpgrade: MonitorSecurityUpgrade, ) : ViewModel() { /** @@ -189,6 +190,13 @@ class ManagerViewModel @Inject constructor( it.copy(pendingActionsCount = _state.value.pendingActionsCount + incomingShares) } } + viewModelScope.launch { + monitorSecurityUpgrade().collect { + if (it) { + setShouldAlertUserAboutSecurityUpgrade(true) + } + } + } viewModelScope.launch { val outgoingShares = @@ -223,13 +231,6 @@ class ManagerViewModel @Inject constructor( .map { Event(it) } .asLiveData() - - private val updateGlobalEvents: Flow> = _updates - .filterIsInstance() - .mapNotNull { (event) -> - event?.let { Event(it) } - } - private fun checkItemForInbox(updatedNodes: List) { //Verify is it is a new item to the inbox inboxNode?.let { node -> @@ -456,19 +457,6 @@ class ManagerViewModel @Inject constructor( */ val activeSubscription: MegaPurchase? get() = getActiveSubscription() - /** - * Check global events updates for [MegaEvent.EVENT_UPGRADE_SECURITY] - */ - fun monitorGlobalEventUpgradeForUpgradeSecurity() { - viewModelScope.launch { - updateGlobalEvents.collect { megaEvent -> - if (megaEvent.peekContent().type == MegaEvent.EVENT_UPGRADE_SECURITY) { - setShouldAlertUserAboutSecurityUpgrade(true) - } - } - } - } - /** * Set the security upgrade alert state * diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModelTest.kt index d1042310d6f..8ae795bfb05 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/fingerprintauth/SecurityUpgradeViewModelTest.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.presentation.fingerprintauth.SecurityUpgradeViewModel import mega.privacy.android.domain.usecase.UpgradeSecurity +import mega.privacy.android.domain.usecase.filenode.SetSecurityUpgrade import org.junit.After import org.junit.Before import org.junit.Test @@ -19,11 +20,12 @@ class SecurityUpgradeViewModelTest { private lateinit var underTest: SecurityUpgradeViewModel private val upgradeSecurity = mock() + private val setSecurityUpgrade = mock() @Before fun setUp() { Dispatchers.setMain(UnconfinedTestDispatcher()) - underTest = SecurityUpgradeViewModel(upgradeSecurity) + underTest = SecurityUpgradeViewModel(upgradeSecurity, setSecurityUpgrade) } @After @@ -36,4 +38,4 @@ class SecurityUpgradeViewModelTest { underTest.upgradeAccountSecurity() verify(upgradeSecurity).invoke() } -} \ No newline at end of file +} diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt index 8e143b15dff..386e36c7f40 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/manager/ManagerViewModelTest.kt @@ -66,6 +66,7 @@ class ManagerViewModelTest { private val getNumUnreadUserAlerts = mock() private val hasInboxChildren = mock() private val monitorContactRequestUpdates = mock() + private val monitorSecurityUpgrade = MutableStateFlow(false) private val sendStatisticsMediaDiscovery = mock() private val savedStateHandle = SavedStateHandle(mapOf()) private val monitorMyAvatarFile = mock() @@ -148,6 +149,7 @@ class ManagerViewModelTest { getFeatureFlagValue = getFeatureFlagValue, getUnverifiedIncomingShares = getUnverifiedIncomingShares, getUnverifiedOutgoingShares = getUnverifiedOutgoingShares, + monitorSecurityUpgrade = { monitorSecurityUpgrade } ) } diff --git a/data/src/main/java/mega/privacy/android/data/facade/AppEventFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/AppEventFacade.kt index d63a9e9fed2..4ff4165e53f 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/AppEventFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/AppEventFacade.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.shareIn import mega.privacy.android.data.gateway.AppEventGateway import mega.privacy.android.domain.qualifier.ApplicationScope @@ -25,6 +26,8 @@ internal class AppEventFacade @Inject constructor( private val _isSMSVerificationShownState = MutableStateFlow(false) + private val updateUpgradeSecurityState = MutableStateFlow(false) + override val monitorCameraUploadPauseState = _monitorCameraUploadPauseState.toSharedFlow(appScope) @@ -49,6 +52,13 @@ internal class AppEventFacade @Inject constructor( override fun monitorFailedTransfer(): Flow = _transferFailed.asSharedFlow() + override fun monitorSecurityUpgrade(): Flow = + updateUpgradeSecurityState.asStateFlow() + + override suspend fun setUpgradeSecurity(isSecurityUpgrade: Boolean) { + updateUpgradeSecurityState.value = isSecurityUpgrade + } + override suspend fun broadcastFailedTransfer(isFailed: Boolean) { _transferFailed.emit(isFailed) } diff --git a/data/src/main/java/mega/privacy/android/data/gateway/AppEventGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/AppEventGateway.kt index 9fdb0c9e2d1..796cb0349b7 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/AppEventGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/AppEventGateway.kt @@ -19,6 +19,11 @@ internal interface AppEventGateway { */ suspend fun setSMSVerificationShown(isShown: Boolean) + /** + * Set the status for account security upgrade + */ + suspend fun setUpgradeSecurity(isSecurityUpgrade: Boolean) + /** * check whether SMS Verification Shown or not */ @@ -51,6 +56,12 @@ internal interface AppEventGateway { */ fun monitorFailedTransfer(): Flow + /** + * Monitor transfer failed + * + */ + fun monitorSecurityUpgrade(): Flow + /** * Broadcast transfer failed * diff --git a/data/src/main/java/mega/privacy/android/data/repository/NodeRepositoryImpl.kt b/data/src/main/java/mega/privacy/android/data/repository/NodeRepositoryImpl.kt index 2962a917923..67ed85e36a9 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/NodeRepositoryImpl.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/NodeRepositoryImpl.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import mega.privacy.android.data.extensions.failWithError import mega.privacy.android.data.extensions.getRequestListener +import mega.privacy.android.data.gateway.AppEventGateway import mega.privacy.android.data.gateway.CacheFolderGateway import mega.privacy.android.data.gateway.FileGateway import mega.privacy.android.data.gateway.MegaLocalStorageGateway @@ -82,6 +83,7 @@ internal class NodeRepositoryImpl @Inject constructor( private val chatFilesFolderUserAttributeMapper: ChatFilesFolderUserAttributeMapper, private val streamingGateway: StreamingGateway, private val nodeUpdateMapper: NodeUpdateMapper, + private val appEventGateway: AppEventGateway, ) : NodeRepository { @@ -254,4 +256,11 @@ internal class NodeRepositoryImpl @Inject constructor( megaLocalStorageGateway.getOfflineInformation(nodeHandle) ?.let { offlineNodeInformationMapper(it) } } + + override fun monitorSecurityUpgrade(): Flow = + appEventGateway.monitorSecurityUpgrade() + + override suspend fun setUpgradeSecurity(isSecurityUpgrade: Boolean) { + appEventGateway.setUpgradeSecurity(isSecurityUpgrade) + } } diff --git a/data/src/test/java/mega/privacy/android/data/repository/NodeRepositoryImplTest.kt b/data/src/test/java/mega/privacy/android/data/repository/NodeRepositoryImplTest.kt index 777fd7043e0..c6691dea906 100644 --- a/data/src/test/java/mega/privacy/android/data/repository/NodeRepositoryImplTest.kt +++ b/data/src/test/java/mega/privacy/android/data/repository/NodeRepositoryImplTest.kt @@ -5,6 +5,7 @@ import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest +import mega.privacy.android.data.gateway.AppEventGateway import mega.privacy.android.data.gateway.CacheFolderGateway import mega.privacy.android.data.gateway.FileGateway import mega.privacy.android.data.gateway.MegaLocalStorageGateway @@ -55,6 +56,7 @@ class NodeRepositoryImplTest { private val streamingGateway: StreamingGateway = mock() private val nodeUpdateMapper: NodeUpdateMapper = mock() private val folderNode: FolderNode = mock() + private val appEventGateway: AppEventGateway = mock() @Before fun setup() { @@ -75,7 +77,8 @@ class NodeRepositoryImplTest { fileGateway = fileGateway, chatFilesFolderUserAttributeMapper = chatFilesFolderUserAttributeMapper, streamingGateway = streamingGateway, - nodeUpdateMapper = nodeUpdateMapper + nodeUpdateMapper = nodeUpdateMapper, + appEventGateway = appEventGateway, ) } @@ -135,4 +138,4 @@ class NodeRepositoryImplTest { totalCurrentSizeInBytes = 2000L, ) } -} \ No newline at end of file +} diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/di/InternalFileNodeModule.kt b/domain/src/main/kotlin/mega/privacy/android/domain/di/InternalFileNodeModule.kt index 2ece094583d..f4e6aafe06b 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/di/InternalFileNodeModule.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/di/InternalFileNodeModule.kt @@ -9,6 +9,8 @@ import mega.privacy.android.domain.usecase.DefaultGetFolderTreeInfo import mega.privacy.android.domain.usecase.GetFolderTreeInfo import mega.privacy.android.domain.usecase.IsNodeInInbox import mega.privacy.android.domain.usecase.filenode.GetFileHistoryNumVersions +import mega.privacy.android.domain.usecase.filenode.MonitorSecurityUpgrade +import mega.privacy.android.domain.usecase.filenode.SetSecurityUpgrade /** * module to provide FileNode modules @@ -37,5 +39,14 @@ abstract class InternalFileNodeModule { @Provides fun provideIsNodeInInbox(nodeRepository: NodeRepository): IsNodeInInbox = IsNodeInInbox(nodeRepository::isNodeInInbox) + + @Provides + fun provideMonitorSecurityUpgrade(nodeRepository: NodeRepository): MonitorSecurityUpgrade = + MonitorSecurityUpgrade(nodeRepository::monitorSecurityUpgrade) + + @Provides + fun provideSetSecurityUpgrade(nodeRepository: NodeRepository): SetSecurityUpgrade = + SetSecurityUpgrade(nodeRepository::setUpgradeSecurity) + } -} \ No newline at end of file +} diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/repository/NodeRepository.kt b/domain/src/main/kotlin/mega/privacy/android/domain/repository/NodeRepository.kt index 51d30c4313e..c7bd2bda87a 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/repository/NodeRepository.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/repository/NodeRepository.kt @@ -120,4 +120,18 @@ interface NodeRepository { * @return Offline node information if found */ suspend fun getOfflineNodeInformation(nodeHandle: Long): OfflineNodeInformation? + + /** + * Monitor update upgrade security events + * + * @return + */ + fun monitorSecurityUpgrade(): Flow + + /** + * Set upgrade security + * + * @param isSecurityUpgrade + */ + suspend fun setUpgradeSecurity(isSecurityUpgrade: Boolean) } diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/filenode/MonitorSecurityUpgrade.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/filenode/MonitorSecurityUpgrade.kt new file mode 100644 index 00000000000..8245b3cefb7 --- /dev/null +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/filenode/MonitorSecurityUpgrade.kt @@ -0,0 +1,10 @@ +package mega.privacy.android.domain.usecase.filenode + +import kotlinx.coroutines.flow.Flow + +/** + * Monitor account security upgrade + */ +fun interface MonitorSecurityUpgrade { + operator fun invoke(): Flow +} diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/filenode/SetSecurityUpgrade.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/filenode/SetSecurityUpgrade.kt new file mode 100644 index 00000000000..0e8bdbef318 --- /dev/null +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/filenode/SetSecurityUpgrade.kt @@ -0,0 +1,14 @@ +package mega.privacy.android.domain.usecase.filenode + +/** + * Set account security upgrade + * + */ +fun interface SetSecurityUpgrade { + /** + * Invoke + * + * @param isSecurityUpgrade + */ + suspend operator fun invoke(isSecurityUpgrade: Boolean) +} From 630a1859f500bf9958e166ef0c44c02771a077d6 Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Thu, 9 Mar 2023 14:51:47 +0800 Subject: [PATCH 225/334] AP-111 P-Botched Google Subscription The MR is fixed the issue that cannot upgrade from ProLite to Pro I MR link: https://code.developers.mega.co.nz/mobile/android/android/-/merge_requests/4688 --- .../java/mega/privacy/android/app/BaseActivity.kt | 14 +++++--------- .../android/domain/entity/billing/MegaPurchase.kt | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt index 80d65147ec7..c4568a071eb 100644 --- a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt @@ -93,10 +93,6 @@ import mega.privacy.android.app.utils.Constants.MUTE_NOTIFICATIONS_SNACKBAR_TYPE import mega.privacy.android.app.utils.Constants.NOT_SPACE_SNACKBAR_TYPE import mega.privacy.android.app.utils.Constants.OPEN_FILE_SNACKBAR_TYPE import mega.privacy.android.app.utils.Constants.PERMISSIONS_TYPE -import mega.privacy.android.app.utils.Constants.PRO_I -import mega.privacy.android.app.utils.Constants.PRO_II -import mega.privacy.android.app.utils.Constants.PRO_III -import mega.privacy.android.app.utils.Constants.PRO_LITE import mega.privacy.android.app.utils.Constants.REMOVED_BUSINESS_ACCOUNT_BLOCK import mega.privacy.android.app.utils.Constants.RESUME_TRANSFERS_TYPE import mega.privacy.android.app.utils.Constants.SENT_REQUESTS_TYPE @@ -1441,8 +1437,8 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque val image: Int val activeSubscriptionSku = activeSubscription?.sku.orEmpty() - when (activeSubscription?.level) { - PRO_I -> { + when (activeSubscription?.sku) { + Skus.SKU_PRO_I_MONTH, Skus.SKU_PRO_I_YEAR -> { account = R.string.pro1_account image = R.drawable.ic_pro_i_big_crest purchaseMessage.text = StringResourcesUtils.getString( @@ -1450,7 +1446,7 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque else R.string.upgrade_account_successful_pro_1_monthly ) } - PRO_II -> { + Skus.SKU_PRO_II_MONTH, Skus.SKU_PRO_II_YEAR -> { account = R.string.pro2_account image = R.drawable.ic_pro_ii_big_crest purchaseMessage.text = StringResourcesUtils.getString( @@ -1458,7 +1454,7 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque else R.string.upgrade_account_successful_pro_2_monthly ) } - PRO_III -> { + Skus.SKU_PRO_III_MONTH, Skus.SKU_PRO_III_YEAR -> { account = R.string.pro3_account image = R.drawable.ic_pro_iii_big_crest purchaseMessage.text = StringResourcesUtils.getString( @@ -1466,7 +1462,7 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque else R.string.upgrade_account_successful_pro_3_monthly ) } - PRO_LITE -> { + Skus.SKU_PRO_LITE_MONTH, Skus.SKU_PRO_LITE_YEAR -> { account = R.string.prolite_account color = R.color.orange_400_orange_300 image = R.drawable.ic_lite_big_crest diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/entity/billing/MegaPurchase.kt b/domain/src/main/kotlin/mega/privacy/android/domain/entity/billing/MegaPurchase.kt index 6bb72ee9575..77bfb7d0cc6 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/entity/billing/MegaPurchase.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/entity/billing/MegaPurchase.kt @@ -32,7 +32,7 @@ data class MegaPurchase( * product level */ val level: Int = when (sku) { - Skus.SKU_PRO_LITE_MONTH, Skus.SKU_PRO_LITE_YEAR -> 4 + Skus.SKU_PRO_LITE_MONTH, Skus.SKU_PRO_LITE_YEAR -> 0 Skus.SKU_PRO_I_MONTH, Skus.SKU_PRO_I_YEAR -> 1 Skus.SKU_PRO_II_MONTH, Skus.SKU_PRO_II_YEAR -> 2 Skus.SKU_PRO_III_MONTH, Skus.SKU_PRO_III_YEAR -> 3 From f24643d66b6db4f27cf82be53d0fe706ea8f31e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yenel=20Rodr=C3=ADguez=20Hern=C3=A1ndez?= Date: Fri, 10 Mar 2023 02:55:23 +1300 Subject: [PATCH 226/334] T4662857 Crash removing permissions with the app in background --- .../clouddrive/FileBrowserFragment.kt | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt index 076e41133f6..711ff3d3142 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt @@ -443,25 +443,7 @@ class FileBrowserFragment : RotatableFragment() { if (!isAdded) { return null } - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - fileBrowserViewModel.state.collect { - hideMultipleSelect() - setNodes(it.nodes.toMutableList()) - recyclerView?.invalidate() - } - } - } - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, - EventObserver { showSortByPanel() } - ) - } - } - LiveEventBus.get(EVENT_SHOW_MEDIA_DISCOVERY, Unit::class.java) - .observe(this) { showMediaDiscovery(true) } Timber.d("Fragment ADDED") if (aB == null) { aB = (activity as? AppCompatActivity)?.supportActionBar @@ -613,18 +595,15 @@ class FileBrowserFragment : RotatableFragment() { animateNode(fileBrowserViewModel.state.value.nodes) } - fileBrowserViewModel.state.flowWithLifecycle( - viewLifecycleOwner.lifecycle, - Lifecycle.State.RESUMED - ).onEach { - mediaDiscoveryViewSettings = it.mediaDiscoveryViewSettings - }.launchIn(viewLifecycleOwner.lifecycleScope) - return view } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setupObservers() + } + + private fun setupObservers() { observeDragSupportEvents( viewLifecycleOwner, recyclerView, @@ -634,6 +613,33 @@ class FileBrowserFragment : RotatableFragment() { viewLifecycleOwner.collectFlow(sortByHeaderViewModel.state) { state -> handleNewViewType(state.viewType) } + + fileBrowserViewModel.state.flowWithLifecycle( + viewLifecycleOwner.lifecycle, + Lifecycle.State.RESUMED + ).onEach { + mediaDiscoveryViewSettings = it.mediaDiscoveryViewSettings + }.launchIn(viewLifecycleOwner.lifecycleScope) + + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + fileBrowserViewModel.state.collect { + hideMultipleSelect() + setNodes(it.nodes.toMutableList()) + recyclerView?.invalidate() + } + } + } + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { + sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, + EventObserver { showSortByPanel() } + ) + } + } + + LiveEventBus.get(EVENT_SHOW_MEDIA_DISCOVERY, Unit::class.java) + .observe(this) { showMediaDiscovery(true) } } /** From 57485d6f61dd4967404c6444fed449205947d0c9 Mon Sep 17 00:00:00 2001 From: Raquel Garcia Chico Date: Fri, 10 Mar 2023 10:00:00 +1300 Subject: [PATCH 227/334] MEET-1208 AND - user starts or joins a scheduled meeting --- .../app/main/megachat/ChatActivity.java | 24 ++-- .../MeetingListBottomSheetDialogFragment.kt | 70 +++++++--- .../app/meeting/list/MeetingListFragment.kt | 2 +- .../app/meeting/list/MeetingListViewModel.kt | 71 +++++++--- .../app/presentation/chat/ChatViewModel.kt | 131 ++++++++---------- .../app/presentation/chat/model/ChatState.kt | 4 + .../app/utils/permission/PermissionUtils.kt | 66 +++++++++ .../data/repository/CallRepositoryImpl.kt | 1 + .../usecase/meeting/DefaultAnswerChatCall.kt | 9 +- .../usecase/meeting/DefaultOpenOrStartCall.kt | 9 +- .../meeting/DefaultStartChatCallNoRinging.kt | 25 ++-- 11 files changed, 271 insertions(+), 141 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java b/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java index d00cc10aeb5..207da92ccc7 100644 --- a/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java @@ -2067,7 +2067,7 @@ private void collectFlows() { } ScheduledMeetingStatus schedMeetStatus = chatState.getScheduledMeetingStatus(); - if (schedMeetStatus != null && (schedMeetStatus == ScheduledMeetingStatus.NotStarted || + if (chatRoom.isActive() && !chatRoom.isArchived() && schedMeetStatus != null && (schedMeetStatus == ScheduledMeetingStatus.NotStarted || schedMeetStatus == ScheduledMeetingStatus.NotJoined)) { startOrJoinMeetingBanner.setText(schedMeetStatus == ScheduledMeetingStatus.NotStarted ? @@ -2079,14 +2079,17 @@ private void collectFlows() { startOrJoinMeetingBanner.setVisibility(View.GONE); } - if (chatState.getCurrentCallChatId() != MEGACHAT_INVALID_HANDLE) { + long callChatId = chatState.getCurrentCallChatId(); + + if (callChatId != MEGACHAT_INVALID_HANDLE) { + Timber.d("Open call with chat Id " + callChatId); viewModel.removeCurrentCall(); Intent intentMeeting = new Intent(chatActivity, MeetingActivity.class); intentMeeting.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intentMeeting.setAction(MEETING_ACTION_IN); - intentMeeting.putExtra(MEETING_CHAT_ID, chatState.getCurrentCallChatId()); - intentMeeting.putExtra(MeetingActivity.MEETING_AUDIO_ENABLE, true); - intentMeeting.putExtra(MeetingActivity.MEETING_VIDEO_ENABLE, false); + intentMeeting.putExtra(MEETING_CHAT_ID, callChatId); + intentMeeting.putExtra(MeetingActivity.MEETING_AUDIO_ENABLE, chatState.getCurrentCallAudioStatus()); + intentMeeting.putExtra(MeetingActivity.MEETING_VIDEO_ENABLE, chatState.getCurrentCallVideoStatus()); startActivity(intentMeeting); } @@ -3700,7 +3703,7 @@ private void checkCallInThisChat() { */ private void startCall() { enableCallMenuItems(false); - viewModel.onCallTap(chatRoom.getChatId(), startVideo, true); + viewModel.onCallTap(startVideo); } private void enableCallMenuItems(Boolean enable) { @@ -3833,7 +3836,9 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in break; case REQUEST_CAMERA: - startCall(); + if(checkPermissionsCall()) { + startCall(); + } break; case REQUEST_CAMERA_TAKE_PICTURE: @@ -4591,7 +4596,10 @@ public void onClick(View v) { break; case R.id.start_or_join_meeting_banner: - viewModel.startOrJoinSchedMeeting(); + if (checkPermissionsCall()) { + startVideo = false; + startCall(); + } break; } diff --git a/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListBottomSheetDialogFragment.kt b/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListBottomSheetDialogFragment.kt index a027e84171d..d7d86e14acb 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListBottomSheetDialogFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListBottomSheetDialogFragment.kt @@ -7,6 +7,8 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams @@ -27,6 +29,8 @@ import mega.privacy.android.app.presentation.meeting.RecurringMeetingInfoActivit import mega.privacy.android.app.presentation.meeting.ScheduledMeetingInfoActivity import mega.privacy.android.app.utils.ChatUtil import mega.privacy.android.app.utils.Constants +import mega.privacy.android.app.utils.permission.PermissionUtils.checkCallPermissions +import mega.privacy.android.app.utils.permission.PermissionUtils.requestCallPermissions import mega.privacy.android.app.utils.setImageRequestFromFilePath import mega.privacy.android.app.utils.view.TextDrawable import mega.privacy.android.domain.entity.chat.MeetingRoomItem @@ -56,6 +60,8 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { ContextCompat.getColor(requireContext(), R.color.grey_012_white_012) } + private var currentMeeting: MeetingRoomItem? = null + private lateinit var binding: BottomSheetMeetingDetailBinding private val chatId by lazy { @@ -63,6 +69,7 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { } private val viewModel by viewModels({ requireParentFragment() }) + private lateinit var permissionsRequest: ActivityResultLauncher> override fun onCreateView( inflater: LayoutInflater, @@ -78,9 +85,34 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { @SuppressLint("RestrictedApi") height = dpToPx(requireContext(), 71).toInt() } + permissionsRequest = getCallPermissionsRequest() + return binding.root } + /** + * Get call permissions request + */ + private fun getCallPermissionsRequest() = + registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { + if (checkCallPermissions(requireActivity())) { + currentMeeting?.let { room -> + when (room.scheduledMeetingStatus) { + ScheduledMeetingStatus.NotStarted -> viewModel.startSchedMeeting( + room.chatId, + room.schedId + ) + ScheduledMeetingStatus.NotJoined -> viewModel.joinSchedMeeting( + room.chatId + ) + else -> {} + } + } + + dismissAllowingStateLoss() + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) viewLifecycleOwner.collectFlow( @@ -92,6 +124,7 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { private fun showMeeting(room: MeetingRoomItem?) { requireNotNull(room) { "Meeting not found" } + currentMeeting = room binding.header.txtTitle.text = room.title binding.header.txtTimestamp.setText( @@ -106,7 +139,8 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { binding.header.groupThumbnails.isVisible = false binding.header.imgThumbnail.isVisible = false } else { - val firstUserPlaceholder = getImagePlaceholder(room.firstUserChar.toString(), room.firstUserColor) + val firstUserPlaceholder = + getImagePlaceholder(room.firstUserChar.toString(), room.firstUserColor) if (room.isSingleMeeting()) { binding.header.imgThumbnail.hierarchy.setPlaceholderImage( firstUserPlaceholder, @@ -116,7 +150,8 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { binding.header.groupThumbnails.isVisible = false binding.header.imgThumbnail.isVisible = true } else { - val lastUserPlaceholder = getImagePlaceholder(room.lastUserChar.toString(), room.lastUserColor) + val lastUserPlaceholder = + getImagePlaceholder(room.lastUserChar.toString(), room.lastUserColor) binding.header.imgThumbnailGroupFirst.hierarchy.setPlaceholderImage( firstUserPlaceholder, ScalingUtils.ScaleType.FIT_CENTER @@ -132,7 +167,7 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { } } - binding.btnCancel.isVisible = room.isPending + binding.btnCancel.isVisible = false // Disabled until feature implementation binding.dividerArchive.isVisible = binding.btnCancel.isVisible binding.btnRecurringMeeting.isVisible = room.isRecurring() @@ -151,8 +186,6 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { binding.btnStartOrJoinSchedMeeting.isVisible = room.isActive && - room.isScheduledMeeting() && - room.isPending && room.scheduledMeetingStatus != ScheduledMeetingStatus.Joined binding.dividerStartOrJoinSchedMeeting.isVisible = binding.btnStartOrJoinSchedMeeting.isVisible @@ -180,17 +213,7 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { } binding.btnStartOrJoinSchedMeeting.setOnClickListener { - room.schedId?.let { schedId -> - when (room.scheduledMeetingStatus) { - ScheduledMeetingStatus.NotStarted -> viewModel.startSchedMeeting( - room.chatId, - schedId - ) - ScheduledMeetingStatus.NotJoined -> viewModel.joinSchedMeeting(room.chatId) - else -> {} - } - } - dismissAllowingStateLoss() + requestCallPermissions(permissionsRequest) } binding.btnInfo.setOnClickListener { @@ -211,16 +234,20 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { if (room.isMuted) { binding.btnMute.setText(R.string.general_unmute) - binding.btnMute.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_unmute, + binding.btnMute.setCompoundDrawablesRelativeWithIntrinsicBounds( + R.drawable.ic_unmute, 0, 0, - 0) + 0 + ) } else { binding.btnMute.setText(R.string.general_mute) - binding.btnMute.setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_mute, + binding.btnMute.setCompoundDrawablesRelativeWithIntrinsicBounds( + R.drawable.ic_mute, 0, 0, - 0) + 0 + ) } binding.btnMute.setOnClickListener { if (room.isMuted) { @@ -228,7 +255,8 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { .controlMuteNotificationsOfAChat( requireContext(), Constants.NOTIFICATIONS_ENABLED, - chatId) + chatId + ) } else { ChatUtil.createMuteNotificationsAlertDialogOfAChat(requireActivity(), chatId) } diff --git a/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListFragment.kt b/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListFragment.kt index f7308a649a3..7d43362c511 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListFragment.kt @@ -174,7 +174,7 @@ class MeetingListFragment : Fragment() { binding.viewEmptySearch.root.isVisible = false } - if (state.currentCallChatId != null) { + state.currentCallChatId?.let { viewModel.removeCurrentCall() launchChatCallScreen(state.currentCallChatId) } diff --git a/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListViewModel.kt b/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListViewModel.kt index 9b6aa9c510d..3931fe15376 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListViewModel.kt @@ -29,12 +29,14 @@ import mega.privacy.android.app.utils.CallUtil import mega.privacy.android.app.utils.RxUtil.blockingGetOrNull import mega.privacy.android.data.gateway.DeviceGateway import mega.privacy.android.data.gateway.api.MegaChatApiGateway +import mega.privacy.android.domain.entity.chat.ChatCall import mega.privacy.android.domain.entity.chat.MeetingRoomItem import mega.privacy.android.domain.usecase.ArchiveChat import mega.privacy.android.domain.usecase.GetMeetings import mega.privacy.android.domain.usecase.LeaveChat import mega.privacy.android.domain.usecase.SignalChatPresenceActivity import mega.privacy.android.domain.usecase.meeting.AnswerChatCall +import mega.privacy.android.domain.usecase.meeting.OpenOrStartCall import mega.privacy.android.domain.usecase.meeting.StartChatCallNoRinging import timber.log.Timber import javax.inject.Inject @@ -50,6 +52,7 @@ import javax.inject.Inject * @property meetingLastTimestampMapper [MeetingLastTimestampMapper] * @property scheduledMeetingTimestampMapper [ScheduledMeetingTimestampMapper] * @property startChatCallNoRinging [StartChatCallNoRinging] + * @property openOrStartCall [OpenOrStartCall] * @property answerChatCall [AnswerChatCall] * @property deviceGateway [DeviceGateway] * @property chatManagement [ChatManagement] @@ -68,6 +71,7 @@ class MeetingListViewModel @Inject constructor( private val meetingLastTimestampMapper: MeetingLastTimestampMapper, private val scheduledMeetingTimestampMapper: ScheduledMeetingTimestampMapper, private val startChatCallNoRinging: StartChatCallNoRinging, + private val openOrStartCall: OpenOrStartCall, private val answerChatCall: AnswerChatCall, private val deviceGateway: DeviceGateway, private val chatManagement: ChatManagement, @@ -175,26 +179,24 @@ class MeetingListViewModel @Inject constructor( * * @param chatId Chat Id. */ - fun joinSchedMeeting(chatId: Long) = + fun joinSchedMeeting(chatId: Long) { viewModelScope.launch { + Timber.d("Answer meeting") answerChatCall( chatId = chatId, video = false, audio = true )?.let { call -> call.chatId.takeIf { it != megaChatApiGateway.getChatInvalidHandle() } - ?.let { callChatId -> + ?.let { chatManagement.removeJoiningCallChatId(chatId) rtcAudioManagerGateway.removeRTCAudioManagerRingIn() - chatManagement.setSpeakerStatus(callChatId, call.hasLocalVideo) - chatManagement.setRequestSentCall(call.callId, true) CallUtil.clearIncomingCallNotification(call.callId) - passcodeManagement.showPasscodeScreen = true - MegaApplication.getInstance().openCallService(callChatId) - state.update { it.copy(currentCallChatId = callChatId) } + openCurrentCall(call) } } } + } /** * Start scheduled meeting call @@ -202,24 +204,49 @@ class MeetingListViewModel @Inject constructor( * @param chatId Chat Id. * @param schedId Scheduled meeting Id. */ - fun startSchedMeeting(chatId: Long, schedId: Long) = + fun startSchedMeeting(chatId: Long, schedId: Long?) { viewModelScope.launch { - startChatCallNoRinging( - chatId = chatId, - schedId = schedId, - enabledVideo = false, - enabledAudio = true - )?.let { call -> - call.chatId.takeIf { it != megaChatApiGateway.getChatInvalidHandle() } - ?.let { callChatId -> - chatManagement.setSpeakerStatus(callChatId, false) - chatManagement.setRequestSentCall(call.callId, true) - passcodeManagement.showPasscodeScreen = true - MegaApplication.getInstance().openCallService(callChatId) - state.update { it.copy(currentCallChatId = callChatId) } - } + if (schedId == null || schedId == megaChatApiGateway.getChatInvalidHandle()) { + Timber.d("Start meeting") + openOrStartCall( + chatId = chatId, + video = false, + audio = true + )?.let { call -> + call.chatId.takeIf { it != megaChatApiGateway.getChatInvalidHandle() } + ?.let { + openCurrentCall(call) + } + } + } else { + Timber.d("Start scheduled meeting") + startChatCallNoRinging( + chatId = chatId, + schedId = schedId, + enabledVideo = false, + enabledAudio = true + )?.let { call -> + call.chatId.takeIf { it != megaChatApiGateway.getChatInvalidHandle() } + ?.let { + openCurrentCall(call) + } + } } } + } + + /** + * Open current call + * + * @param call [ChatCall] + */ + private fun openCurrentCall(call: ChatCall) { + chatManagement.setSpeakerStatus(call.chatId, false) + chatManagement.setRequestSentCall(call.callId, call.isOutgoing) + passcodeManagement.showPasscodeScreen = true + MegaApplication.getInstance().openCallService(call.chatId) + state.update { it.copy(currentCallChatId = call.chatId) } + } /** * Remove current chat call diff --git a/app/src/main/java/mega/privacy/android/app/presentation/chat/ChatViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/chat/ChatViewModel.kt index 5d3573f16d9..02a60ed81fa 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/chat/ChatViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/chat/ChatViewModel.kt @@ -22,7 +22,6 @@ import mega.privacy.android.app.presentation.extensions.getState import mega.privacy.android.app.presentation.extensions.isPast import mega.privacy.android.app.utils.CallUtil import mega.privacy.android.data.gateway.api.MegaChatApiGateway -import mega.privacy.android.domain.entity.ChatRequestParamType import mega.privacy.android.domain.entity.StorageState import mega.privacy.android.domain.entity.chat.ChatCall import mega.privacy.android.domain.entity.meeting.ChatCallChanges @@ -34,6 +33,7 @@ import mega.privacy.android.domain.usecase.MonitorStorageStateEvent import mega.privacy.android.domain.usecase.meeting.AnswerChatCall import mega.privacy.android.domain.usecase.meeting.GetChatCall import mega.privacy.android.domain.usecase.meeting.MonitorChatCallUpdates +import mega.privacy.android.domain.usecase.meeting.OpenOrStartCall import mega.privacy.android.domain.usecase.meeting.StartChatCall import mega.privacy.android.domain.usecase.meeting.StartChatCallNoRinging import timber.log.Timber @@ -69,6 +69,7 @@ class ChatViewModel @Inject constructor( private val chatManagement: ChatManagement, private val rtcAudioManagerGateway: RTCAudioManagerGateway, private val startChatCallNoRinging: StartChatCallNoRinging, + private val openOrStartCall: OpenOrStartCall, private val megaChatApiGateway: MegaChatApiGateway, private val getScheduledMeetingByChat: GetScheduledMeetingByChat, private val getChatCall: GetChatCall, @@ -101,57 +102,24 @@ class ChatViewModel @Inject constructor( get() = monitorConnectivity().value /** - * Starts a call. + * Call button clicked * - * @param chatId The chat id. * @param video True, video on. False, video off. - * @param audio True, audio on. False, video off. */ - fun onCallTap(chatId: Long, video: Boolean, audio: Boolean) { - if (chatApiGateway.getChatCall(chatId) != null) { - Timber.d("There is a call, open it") - CallUtil.openMeetingInProgress( - getInstance().applicationContext, - chatId, - true, - passcodeManagement - ) - return - } - + fun onCallTap(video: Boolean) { MegaApplication.isWaitingForCall = false - cameraGateway.setFrontCamera() - viewModelScope.launch { - runCatching { - startChatCall(chatId, video, audio) - }.onFailure { exception -> - Timber.e(exception) - }.onSuccess { resultStartCall -> - val resultChatId = resultStartCall.chatHandle - if (resultChatId != null) { - val videoEnable = resultStartCall.flag - val paramType = resultStartCall.paramType - val audioEnable: Boolean = paramType == ChatRequestParamType.Video - - CallUtil.addChecksForACall(resultChatId, videoEnable) - - chatApiGateway.getChatCall(resultChatId)?.let { call -> - if (call.isOutgoing) { - chatManagement.setRequestSentCall(call.callId, true) - } - } - - CallUtil.openMeetingWithAudioOrVideo( - getInstance().applicationContext, - resultChatId, - audioEnable, - videoEnable, passcodeManagement - ) - - } - } + when { + _state.value.schedId == null || _state.value.schedId == megaChatApiGateway.getChatInvalidHandle() -> startCall( + video = video + ) + _state.value.scheduledMeetingStatus == ScheduledMeetingStatus.NotStarted -> startSchedMeeting() + _state.value.scheduledMeetingStatus == ScheduledMeetingStatus.NotJoined -> answerCall( + _state.value.chatId, + video = false, + audio = true + ) } } @@ -273,17 +241,19 @@ class ChatViewModel @Inject constructor( } /** - * Start or join scheduled meeting + * Start call + * + * @param video True, video on. False, video off. */ - fun startOrJoinSchedMeeting() { - when (_state.value.scheduledMeetingStatus) { - ScheduledMeetingStatus.NotStarted -> startSchedMeeting() - ScheduledMeetingStatus.NotJoined -> answerCall( - _state.value.chatId, - video = false, - audio = true - ) - else -> {} + private fun startCall(video: Boolean) = viewModelScope.launch { + Timber.d("Start call") + openOrStartCall( + chatId = _state.value.chatId, video = video, audio = true + )?.let { call -> + call.chatId.takeIf { it != megaChatApiGateway.getChatInvalidHandle() }?.let { + Timber.d("Call started") + openCurrentCall(call = call) + } } } @@ -293,17 +263,20 @@ class ChatViewModel @Inject constructor( private fun startSchedMeeting() = viewModelScope.launch { _state.value.schedId?.let { schedId -> - startChatCallNoRinging( - chatId = _state.value.chatId, - schedId = schedId, - enabledVideo = false, - enabledAudio = true - )?.let { call -> - call.chatId.takeIf { it != megaChatApiGateway.getChatInvalidHandle() } - ?.let { - Timber.d("Meeting started") - openCurrentCall(call, true) - } + if (schedId != megaChatApiGateway.getChatInvalidHandle()) { + Timber.d("Start scheduled meeting") + startChatCallNoRinging( + chatId = _state.value.chatId, + schedId = schedId, + enabledVideo = false, + enabledAudio = true + )?.let { call -> + call.chatId.takeIf { it != megaChatApiGateway.getChatInvalidHandle() } + ?.let { + Timber.d("Meeting started") + openCurrentCall(call = call) + } + } } } } @@ -312,21 +285,32 @@ class ChatViewModel @Inject constructor( * Open current call * * @param call [ChatCall] - * @param isRequestSent True, if it's request sent. False, if not. */ - private fun openCurrentCall(call: ChatCall, isRequestSent: Boolean) { + private fun openCurrentCall(call: ChatCall) { chatManagement.setSpeakerStatus(call.chatId, call.hasLocalVideo) - chatManagement.setRequestSentCall(call.callId, isRequestSent) + chatManagement.setRequestSentCall(call.callId, call.isOutgoing) passcodeManagement.showPasscodeScreen = true getInstance().openCallService(call.chatId) - _state.update { it.copy(currentCallChatId = call.chatId) } + _state.update { + it.copy( + currentCallChatId = call.chatId, + currentCallAudioStatus = call.hasLocalAudio, + currentCallVideoStatus = call.hasLocalVideo + ) + } } /** * Remove current chat call */ fun removeCurrentCall() { - _state.update { it.copy(currentCallChatId = -1L) } + _state.update { + it.copy( + currentCallChatId = megaChatApiGateway.getChatInvalidHandle(), + currentCallVideoStatus = false, + currentCallAudioStatus = false + ) + } } /** @@ -341,6 +325,7 @@ class ChatViewModel @Inject constructor( chatManagement.addJoiningCallChatId(chatId) viewModelScope.launch { + Timber.d("Answer call") answerChatCall( chatId = chatId, video = video, @@ -349,7 +334,7 @@ class ChatViewModel @Inject constructor( chatManagement.removeJoiningCallChatId(chatId) rtcAudioManagerGateway.removeRTCAudioManagerRingIn() CallUtil.clearIncomingCallNotification(call.callId) - openCurrentCall(call, false) + openCurrentCall(call) _state.update { it.copy(isCallAnswered = true) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/chat/model/ChatState.kt b/app/src/main/java/mega/privacy/android/app/presentation/chat/model/ChatState.kt index 371a39dda32..f51fad8209c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/chat/model/ChatState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/chat/model/ChatState.kt @@ -13,6 +13,8 @@ import mega.privacy.android.domain.entity.meeting.ScheduledMeetingStatus * @property currentCallChatId Chat id of the call. * @property scheduledMeetingStatus [ScheduledMeetingStatus] * @property schedIsPending True, if scheduled meeting is pending. False, if not. + * @property currentCallAudioStatus True, if audio is on. False, if audio is off. + * @property currentCallVideoStatus True, if video is on. False, if video is off. */ data class ChatState( val chatId: Long = -1L, @@ -23,4 +25,6 @@ data class ChatState( val currentCallChatId: Long = -1L, val scheduledMeetingStatus: ScheduledMeetingStatus? = null, val schedIsPending: Boolean = false, + val currentCallAudioStatus: Boolean = false, + val currentCallVideoStatus: Boolean = false, ) \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/utils/permission/PermissionUtils.kt b/app/src/main/java/mega/privacy/android/app/utils/permission/PermissionUtils.kt index 21b6ae80b81..72575aa06b2 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/permission/PermissionUtils.kt +++ b/app/src/main/java/mega/privacy/android/app/utils/permission/PermissionUtils.kt @@ -1,11 +1,14 @@ package mega.privacy.android.app.utils.permission import android.Manifest.permission.ACCESS_MEDIA_LOCATION +import android.Manifest.permission.BLUETOOTH_CONNECT +import android.Manifest.permission.CAMERA import android.Manifest.permission.POST_NOTIFICATIONS import android.Manifest.permission.READ_EXTERNAL_STORAGE import android.Manifest.permission.READ_MEDIA_AUDIO import android.Manifest.permission.READ_MEDIA_IMAGES import android.Manifest.permission.READ_MEDIA_VIDEO +import android.Manifest.permission.RECORD_AUDIO import android.Manifest.permission.WRITE_EXTERNAL_STORAGE import android.app.Activity import android.content.Context @@ -13,6 +16,7 @@ import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.view.View +import androidx.activity.result.ActivityResultLauncher import androidx.annotation.NonNull import androidx.annotation.RequiresApi import androidx.core.app.ActivityCompat @@ -185,6 +189,30 @@ object PermissionUtils { @RequiresApi(33) fun getNotificationsPermission() = POST_NOTIFICATIONS + /** + * Get RECORD_AUDIO + * + * @return RECORD_AUDIO + */ + @JvmStatic + fun getRecordAudioPermission() = RECORD_AUDIO + + /** + * Get CAMERA + * + * @return CAMERA + */ + @JvmStatic + fun getCameraPermission() = CAMERA + + /** + * Gets BLUETOOTH_CONNECT + * + * @return BLUETOOTH_CONNECT + */ + @RequiresApi(31) + fun getBluetoothConnectPermission() = BLUETOOTH_CONNECT + /** * Checks if should ask for notifications permission. * @@ -207,6 +235,44 @@ object PermissionUtils { } } + /** + * Checks if the required permissions for a call are granted. + * + * @param activity Required Activity for the checks. + * @return True, if permissions are granted. False, if not. + */ + @JvmStatic + fun checkCallPermissions(activity: Activity): Boolean = + hasPermissions(activity, *getListCallPermissionsByVersion()) + + /** + * Ask for call permissions. + * + * @param request + */ + @JvmStatic + fun requestCallPermissions(request: ActivityResultLauncher>) = + request.launch(getListCallPermissionsByVersion()) + + /** + * Get list of call permissions by version + * + * @return List of required permissions + */ + private fun getListCallPermissionsByVersion() = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + arrayOf( + getBluetoothConnectPermission(), + getCameraPermission(), + getRecordAudioPermission() + ) + } else { + arrayOf( + getCameraPermission(), + getRecordAudioPermission(), + ) + } + /** * Displays the Notification Permission Rationale * diff --git a/data/src/main/java/mega/privacy/android/data/repository/CallRepositoryImpl.kt b/data/src/main/java/mega/privacy/android/data/repository/CallRepositoryImpl.kt index c35b47320da..f562aad49fa 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/CallRepositoryImpl.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/CallRepositoryImpl.kt @@ -238,6 +238,7 @@ internal class CallRepositoryImpl @Inject constructor( private fun onRequestCompleted(continuation: Continuation) = { request: MegaChatRequest, error: MegaChatError -> if (error.errorCode == MegaChatError.ERROR_OK) { + Timber.d("Success") continuation.resumeWith(Result.success(chatRequestMapper(request))) } else { Timber.e("Error: ${error.errorString}") diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/meeting/DefaultAnswerChatCall.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/meeting/DefaultAnswerChatCall.kt index 6f584cb9380..3ee0f1ea0e9 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/meeting/DefaultAnswerChatCall.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/meeting/DefaultAnswerChatCall.kt @@ -12,15 +12,18 @@ class DefaultAnswerChatCall @Inject constructor( ) : AnswerChatCall { override suspend fun invoke(chatId: Long, video: Boolean, audio: Boolean): ChatCall? { - runCatching { + if (chatId == -1L) + return null + + return runCatching { callRepository.answerChatCall( chatId, video, audio ) }.fold( - onSuccess = { request -> return callRepository.getChatCall(request.chatHandle) }, - onFailure = { return null } + onSuccess = { request -> callRepository.getChatCall(request.chatHandle) }, + onFailure = { null } ) } } \ No newline at end of file diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/meeting/DefaultOpenOrStartCall.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/meeting/DefaultOpenOrStartCall.kt index d531fd8a99b..5437b62bdad 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/meeting/DefaultOpenOrStartCall.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/meeting/DefaultOpenOrStartCall.kt @@ -12,19 +12,22 @@ class DefaultOpenOrStartCall @Inject constructor( ) : OpenOrStartCall { override suspend fun invoke(chatId: Long, video: Boolean, audio: Boolean): ChatCall? { + if (chatId == -1L) + return null + callRepository.getChatCall(chatId)?.let { call -> return call } - runCatching { + return runCatching { callRepository.startCallRinging( chatId, video, audio ) }.fold( - onSuccess = { request -> return callRepository.getChatCall(request.chatHandle) }, - onFailure = { return null } + onSuccess = { request -> callRepository.getChatCall(request.chatHandle) }, + onFailure = { null } ) } } \ No newline at end of file diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/meeting/DefaultStartChatCallNoRinging.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/meeting/DefaultStartChatCallNoRinging.kt index fc91760c1e4..ffab432593c 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/meeting/DefaultStartChatCallNoRinging.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/meeting/DefaultStartChatCallNoRinging.kt @@ -15,15 +15,20 @@ class DefaultStartChatCallNoRinging @Inject constructor( schedId: Long, enabledVideo: Boolean, enabledAudio: Boolean, - ): ChatCall? = runCatching { - callRepository.startCallNoRinging( - chatId, - schedId, - enabledVideo, - enabledAudio + ): ChatCall? { + if (chatId == -1L) + return null + + return runCatching { + callRepository.startCallNoRinging( + chatId, + schedId, + enabledVideo, + enabledAudio + ) + }.fold( + onSuccess = { request -> callRepository.getChatCall(request.chatHandle) }, + onFailure = { null } ) - }.fold( - onSuccess = { request -> callRepository.getChatCall(request.chatHandle) }, - onFailure = { null } - ) + } } \ No newline at end of file From aa04394b7ddc56303237208e002c65eb71a493a7 Mon Sep 17 00:00:00 2001 From: Luong Hai Date: Fri, 10 Mar 2023 14:11:19 +1300 Subject: [PATCH 228/334] T4662857: Fix App goes to offline mode in case process recreate --- .../java/mega/privacy/android/app/main/ManagerActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt index 3e95cac9d45..376d3d8b5ee 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt @@ -1608,7 +1608,7 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf rootNode = megaApi.rootNode if (rootNode == null || LoginActivity.isBackFromLoginPage || isHeartBeatAlive) { Timber.d("Action: %s", intent.action) - if (intent.action?.let { handleRedirectIntentActions(it) } == false) { + if (!handleRedirectIntentActions(intent.action)) { refreshSession() } return true @@ -2049,7 +2049,7 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf ) } - private fun handleRedirectIntentActions(action: String): Boolean { + private fun handleRedirectIntentActions(action: String?): Boolean { when (action) { Constants.ACTION_IMPORT_LINK_FETCH_NODES -> { val loginIntent = Intent(this, LoginActivity::class.java) From ed98eb54182defa45a0ade07645e12482b196baf Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Fri, 10 Mar 2023 09:07:28 +0800 Subject: [PATCH 229/334] CU-290 : Create use case to replace ACTION_UPDATE_CU --- .../di/cameraupload/CameraUploadUseCases.kt | 16 ++++++++++ .../android/data/facade/AppEventFacade.kt | 8 +++++ .../android/data/gateway/AppEventGateway.kt | 19 ++++++++++++ .../DefaultCameraUploadRepository.kt | 6 ++++ .../android/data/facade/AppEventFacadeTest.kt | 11 +++++++ .../DefaultCameraUploadRepositoryTest.kt | 31 ++++++++++++++++++- .../repository/CameraUploadRepository.kt | 17 ++++++++++ .../usecase/BroadcastCameraUploadProgress.kt | 14 +++++++++ .../usecase/MonitorCameraUploadProgress.kt | 17 ++++++++++ 9 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 domain/src/main/kotlin/mega/privacy/android/domain/usecase/BroadcastCameraUploadProgress.kt create mode 100644 domain/src/main/kotlin/mega/privacy/android/domain/usecase/MonitorCameraUploadProgress.kt diff --git a/app/src/main/java/mega/privacy/android/app/di/cameraupload/CameraUploadUseCases.kt b/app/src/main/java/mega/privacy/android/app/di/cameraupload/CameraUploadUseCases.kt index 8f6dbaf36bd..5e2e596f6d7 100644 --- a/app/src/main/java/mega/privacy/android/app/di/cameraupload/CameraUploadUseCases.kt +++ b/app/src/main/java/mega/privacy/android/app/di/cameraupload/CameraUploadUseCases.kt @@ -55,6 +55,7 @@ import mega.privacy.android.domain.repository.CameraUploadRepository import mega.privacy.android.domain.repository.FileSystemRepository import mega.privacy.android.domain.repository.NodeRepository import mega.privacy.android.domain.usecase.BackupTimeStampsAndFolderHandle +import mega.privacy.android.domain.usecase.BroadcastCameraUploadProgress import mega.privacy.android.domain.usecase.BroadcastUploadPauseState import mega.privacy.android.domain.usecase.CheckCameraUpload import mega.privacy.android.domain.usecase.CheckEnableCameraUploadsStatus @@ -127,6 +128,7 @@ import mega.privacy.android.domain.usecase.KeepFileNames import mega.privacy.android.domain.usecase.MediaLocalPathExists import mega.privacy.android.domain.usecase.MonitorBatteryInfo import mega.privacy.android.domain.usecase.MonitorCameraUploadPauseState +import mega.privacy.android.domain.usecase.MonitorCameraUploadProgress import mega.privacy.android.domain.usecase.MonitorChargingStoppedState import mega.privacy.android.domain.usecase.RenamePrimaryFolder import mega.privacy.android.domain.usecase.RenameSecondaryFolder @@ -493,6 +495,13 @@ abstract class CameraUploadUseCases { fun provideMonitorCameraUploadPauseState(cameraUploadRepository: CameraUploadRepository): MonitorCameraUploadPauseState = MonitorCameraUploadPauseState(cameraUploadRepository::monitorCameraUploadPauseState) + /** + * Provide the [MonitorCameraUploadProgress] implementation + */ + @Provides + fun provideMonitorCameraUploadProgress(cameraUploadRepository: CameraUploadRepository): MonitorCameraUploadProgress = + MonitorCameraUploadProgress(cameraUploadRepository::monitorCameraUploadProgress) + /** * Provide the [BroadcastUploadPauseState] implementation */ @@ -500,6 +509,13 @@ abstract class CameraUploadUseCases { fun provideBroadcastUploadPauseState(cameraUploadRepository: CameraUploadRepository): BroadcastUploadPauseState = BroadcastUploadPauseState(cameraUploadRepository::broadcastUploadPauseState) + /** + * Provide the [BroadcastCameraUploadProgress] implementation + */ + @Provides + fun provideBroadcastCameraUploadProgress(cameraUploadRepository: CameraUploadRepository): BroadcastCameraUploadProgress = + BroadcastCameraUploadProgress(cameraUploadRepository::broadcastCameraUploadProgress) + /** * Provide the [IsNodeInRubbishOrDeleted] implementation */ diff --git a/data/src/main/java/mega/privacy/android/data/facade/AppEventFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/AppEventFacade.kt index d96b397c614..030f1be7831 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/AppEventFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/AppEventFacade.kt @@ -20,6 +20,7 @@ internal class AppEventFacade @Inject constructor( ) : AppEventGateway { private val _monitorCameraUploadPauseState = MutableSharedFlow() + private val _monitorCameraUploadProgress = MutableSharedFlow>() private val _transferOverQuota = MutableSharedFlow() private val logout = MutableSharedFlow() private val _transferFailed = MutableSharedFlow() @@ -32,9 +33,16 @@ internal class AppEventFacade @Inject constructor( override val monitorCameraUploadPauseState = _monitorCameraUploadPauseState.toSharedFlow(appScope) + override val monitorCameraUploadProgress = + _monitorCameraUploadProgress.toSharedFlow(appScope) + override suspend fun broadcastUploadPauseState() = _monitorCameraUploadPauseState.emit(true) + override suspend fun broadcastCameraUploadProgress(progress: Int, pending: Int) { + _monitorCameraUploadProgress.emit(Pair(progress, pending)) + } + override suspend fun setSMSVerificationShown(isShown: Boolean) { _isSMSVerificationShownState.value = isShown } diff --git a/data/src/main/java/mega/privacy/android/data/gateway/AppEventGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/AppEventGateway.kt index a42879f9e67..3365b77239d 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/AppEventGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/AppEventGateway.kt @@ -9,11 +9,30 @@ internal interface AppEventGateway { */ val monitorCameraUploadPauseState: Flow + /** + * Monitor camera upload progress + * + * The value returned is a Pair of + * + * [Int] value representing progress between 0 and 100; + * [Int] value representing pending elements waiting for upload + */ + val monitorCameraUploadProgress: Flow> + /** * Broadcast upload pause state */ suspend fun broadcastUploadPauseState() + /** + * Broadcast camera upload progress + * + * @param progress represents progress between 0 and 100 + * @param pending represents elements waiting for upload + */ + suspend fun broadcastCameraUploadProgress(progress: Int, pending: Int) + + /** * Set the status for SMSVerification */ diff --git a/data/src/main/java/mega/privacy/android/data/repository/DefaultCameraUploadRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/DefaultCameraUploadRepository.kt index ccc53c2eccf..fbdbf66c73b 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/DefaultCameraUploadRepository.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/DefaultCameraUploadRepository.kt @@ -519,8 +519,14 @@ internal class DefaultCameraUploadRepository @Inject constructor( override fun monitorCameraUploadPauseState() = appEventGateway.monitorCameraUploadPauseState + override fun monitorCameraUploadProgress(): Flow> = + appEventGateway.monitorCameraUploadProgress + override suspend fun broadcastUploadPauseState() = appEventGateway.broadcastUploadPauseState() + override suspend fun broadcastCameraUploadProgress(progress: Int, pending: Int) = + appEventGateway.broadcastCameraUploadProgress(progress, pending) + override fun monitorBatteryInfo() = broadcastReceiverGateway.monitorBatteryInfo override fun monitorChargingStoppedInfo() = broadcastReceiverGateway.monitorChargingStoppedState diff --git a/data/src/test/java/mega/privacy/android/data/facade/AppEventFacadeTest.kt b/data/src/test/java/mega/privacy/android/data/facade/AppEventFacadeTest.kt index b3f8e4aca82..0f6ce92fd08 100644 --- a/data/src/test/java/mega/privacy/android/data/facade/AppEventFacadeTest.kt +++ b/data/src/test/java/mega/privacy/android/data/facade/AppEventFacadeTest.kt @@ -35,6 +35,17 @@ class AppEventFacadeTest { } } + @Test + fun `test that broadcast camera upload progress fires an event`() = runTest { + val expected = Pair(50, 25) + underTest.monitorCameraUploadProgress.test { + underTest.broadcastCameraUploadProgress(expected.first, expected.second) + + val actual = awaitItem() + assertThat(actual).isEqualTo(actual) + } + } + @Test fun `test that set SMS Verification Shown set the correct state`() = runTest { underTest.setSMSVerificationShown(true) diff --git a/data/src/test/java/mega/privacy/android/data/repository/DefaultCameraUploadRepositoryTest.kt b/data/src/test/java/mega/privacy/android/data/repository/DefaultCameraUploadRepositoryTest.kt index aa8c8c7d6fb..5ca74711c3d 100644 --- a/data/src/test/java/mega/privacy/android/data/repository/DefaultCameraUploadRepositoryTest.kt +++ b/data/src/test/java/mega/privacy/android/data/repository/DefaultCameraUploadRepositoryTest.kt @@ -5,8 +5,10 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest +import mega.privacy.android.data.gateway.AppEventGateway import mega.privacy.android.data.gateway.CameraUploadMediaGateway import mega.privacy.android.data.gateway.FileAttributeGateway import mega.privacy.android.data.gateway.MegaLocalStorageGateway @@ -56,6 +58,7 @@ class DefaultCameraUploadRepositoryTest { private val mediaStoreFileTypeUriWrapper = mock() private val cameraUploadsHandlesMapper = mock() private val videoCompressorGateway = mock() + private val appEventGateway = mock() private val fakeRecord = SyncRecord( id = 0, @@ -86,7 +89,7 @@ class DefaultCameraUploadRepositoryTest { mediaStoreFileTypeUriMapper = mediaStoreFileTypeUriWrapper, cameraUploadsHandlesMapper = cameraUploadsHandlesMapper, ioDispatcher = UnconfinedTestDispatcher(), - appEventGateway = mock(), + appEventGateway = appEventGateway, broadcastReceiverGateway = mock(), videoQualityIntMapper = ::videoQualityToInt, videoQualityMapper = ::toVideoQuality, @@ -545,4 +548,30 @@ class DefaultCameraUploadRepositoryTest { } } } + + @Test + fun `test that broadcasting camera upload progress call event gateway camera upload progress with appropriate value`() { + runTest { + val expected = Pair(50, 25) + underTest.broadcastCameraUploadProgress( + progress = expected.first, + pending = expected.second + ) + verify(appEventGateway).broadcastCameraUploadProgress( + progress = expected.first, + pending = expected.second + ) + } + } + + @Test + fun `test that monitor camera upload progress returns the result of event gateway monitor camera upload progress`() { + runTest { + val progress1 = Pair(50, 25) + val progress2 = Pair(51, 24) + val expected = flowOf(progress1, progress2) + whenever(appEventGateway.monitorCameraUploadProgress).thenReturn(expected) + assertThat(underTest.monitorCameraUploadProgress()).isEqualTo(expected) + } + } } diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/repository/CameraUploadRepository.kt b/domain/src/main/kotlin/mega/privacy/android/domain/repository/CameraUploadRepository.kt index fbc7f7bcb1d..5e6a1adcead 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/repository/CameraUploadRepository.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/repository/CameraUploadRepository.kt @@ -472,11 +472,28 @@ interface CameraUploadRepository { */ fun monitorCameraUploadPauseState(): Flow + /** + * Monitor camera upload progress + * + * @return a flow of Pair of + * [Int] value representing progress between 0 and 100; + * [Int] value representing pending elements waiting for upload + */ + fun monitorCameraUploadProgress(): Flow> + /** * Broadcast upload pause state */ suspend fun broadcastUploadPauseState() + /** + * Broadcast camera upload progress + * + * @param progress represents progress between 0 and 100 + * @param pending represents elements waiting for upload + */ + suspend fun broadcastCameraUploadProgress(progress: Int, pending: Int) + /** * monitor battery info */ diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/BroadcastCameraUploadProgress.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/BroadcastCameraUploadProgress.kt new file mode 100644 index 00000000000..9376a99fe44 --- /dev/null +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/BroadcastCameraUploadProgress.kt @@ -0,0 +1,14 @@ +package mega.privacy.android.domain.usecase + +/** + * Broadcast Camera Upload Pause State + */ +fun interface BroadcastCameraUploadProgress { + /** + * Invoke + * + * @param progress value representing progress between 0 and 100; + * @param pending value representing pending elements waiting for upload + */ + suspend operator fun invoke(progress: Int, pending: Int) +} diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/MonitorCameraUploadProgress.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/MonitorCameraUploadProgress.kt new file mode 100644 index 00000000000..43e95ce75aa --- /dev/null +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/MonitorCameraUploadProgress.kt @@ -0,0 +1,17 @@ +package mega.privacy.android.domain.usecase + +import kotlinx.coroutines.flow.Flow + +/** + * Monitor camera upload pause state + */ +fun interface MonitorCameraUploadProgress { + /** + * Invoke + * + * @return flow of Pair of + * [Int] value representing progress between 0 and 100; + * [Int] value representing pending elements waiting for upload + */ + operator fun invoke(): Flow> +} From d518b09160e4157451a88393e3f33a7802f3a93b Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Fri, 10 Mar 2023 22:36:46 +1300 Subject: [PATCH 230/334] CU-289: Replace ACTION_UPDATE_CU with use case --- .../app/constants/BroadcastConstants.kt | 5 +- .../app/jobservices/CameraUploadsService.kt | 69 +++++++++------- .../app/presentation/photos/PhotosFragment.kt | 43 ---------- .../timeline/viewmodel/TimelineViewModel.kt | 32 ++++++++ .../app/di/TestCameraUploadUseCases.kt | 10 +++ .../viewmodel/TimelineViewModelTest.kt | 80 ++++++++++++++++++- 6 files changed, 160 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/constants/BroadcastConstants.kt b/app/src/main/java/mega/privacy/android/app/constants/BroadcastConstants.kt index 083a543443e..98d7da284b8 100644 --- a/app/src/main/java/mega/privacy/android/app/constants/BroadcastConstants.kt +++ b/app/src/main/java/mega/privacy/android/app/constants/BroadcastConstants.kt @@ -62,7 +62,6 @@ object BroadcastConstants { const val ACTION_REFRESH_CLEAR_OFFLINE_SETTING = "ACTION_REFRESH_CLEAR_OFFLINE_SETTING" const val ACTION_UPDATE_RB_SCHEDULER = "ACTION_UPDATE_RB_SCHEDULER" const val ACTION_UPDATE_RETENTION_TIME = "ACTION_UPDATE_RETENTION_TIME" - const val ACTION_UPDATE_CU = "ACTION_UPDATE_CU" // Broadcasts' extras const val EVENT_TEXT = "EVENT_TEXT" @@ -87,11 +86,9 @@ object BroadcastConstants { const val RETENTION_TIME = "RETENTION_TIME" const val PENDING_MESSAGE_ID = "PENDING_MESSAGE_ID" const val ERROR_MESSAGE_TEXT = "ERROR_MESSAGE_TEXT" - const val PROGRESS = "PROGRESS" - const val PENDING_TRANSFERS = "PENDING_TRANSFERS" const val NODE_NAME = "NODE_NAME" const val NODE_HANDLE = "NODE_HANDLE" const val NODE_LOCAL_PATH = "NODE_LOCAL_PATH" const val OFFLINE_AVAILABLE = "OFFLINE_AVAILABLE" const val IS_OPEN_WITH = "IS_OPEN_WITH" -} \ No newline at end of file +} diff --git a/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt b/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt index 47b1ad8610e..01a439f27f3 100644 --- a/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt +++ b/app/src/main/java/mega/privacy/android/app/jobservices/CameraUploadsService.kt @@ -26,6 +26,7 @@ import kotlinx.coroutines.ensureActive import kotlinx.coroutines.isActive import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import mega.privacy.android.app.AndroidCompletedTransfer import mega.privacy.android.app.LegacyDatabaseHandler import mega.privacy.android.app.MegaApplication @@ -75,7 +76,6 @@ import mega.privacy.android.app.utils.FileUtil import mega.privacy.android.app.utils.ImageProcessor import mega.privacy.android.app.utils.JobUtil import mega.privacy.android.app.utils.PreviewUtils -import mega.privacy.android.app.utils.StringResourcesUtils import mega.privacy.android.app.utils.ThumbnailUtils import mega.privacy.android.app.utils.Util import mega.privacy.android.data.mapper.camerauploads.SyncRecordTypeIntMapper @@ -87,6 +87,8 @@ import mega.privacy.android.domain.entity.SyncRecordType import mega.privacy.android.domain.entity.SyncStatus import mega.privacy.android.domain.entity.VideoCompressionState import mega.privacy.android.domain.qualifier.IoDispatcher +import mega.privacy.android.domain.qualifier.MainDispatcher +import mega.privacy.android.domain.usecase.BroadcastCameraUploadProgress import mega.privacy.android.domain.usecase.ClearSyncRecords import mega.privacy.android.domain.usecase.CompressVideos import mega.privacy.android.domain.usecase.CompressedVideoPending @@ -167,11 +169,6 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback { * is aborted prematurely */ const val EXTRA_ABORTED = "EXTRA_ABORTED" - - /** - * Camera Uploads Cache Folder - */ - private const val CU_CACHE_FOLDER = "cu" } /** @@ -387,12 +384,19 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback { lateinit var syncRecordTypeIntMapper: SyncRecordTypeIntMapper /** - * Coroutine dispatcher for camera upload work + * IO dispatcher for camera upload work */ @IoDispatcher @Inject lateinit var ioDispatcher: CoroutineDispatcher + /** + * Main dispatcher for camera upload work + */ + @MainDispatcher + @Inject + lateinit var mainDispatcher: CoroutineDispatcher + /** * Monitor camera upload pause state */ @@ -549,6 +553,12 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback { @Inject lateinit var deleteCameraUploadTemporaryRootDirectory: DeleteCameraUploadTemporaryRootDirectory + /** + * Broadcast camera upload progress + */ + @Inject + lateinit var broadcastCameraUploadProgress: BroadcastCameraUploadProgress + /** * Coroutine Scope for camera upload work */ @@ -1642,6 +1652,7 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback { if (coroutineScope?.isActive == true) { sendStatusToBackupCenter(aborted = aborted) cancelAllPendingTransfers() + broadcastProgress(100, 0) videoCompressionJob?.cancel() coroutineScope?.cancel(CancellationException(cancelMessage)) } @@ -1825,15 +1836,20 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback { } else { Timber.d("No pending videos, finish") onQueueComplete() - sendBroadcast( - Intent(BroadcastConstants.ACTION_UPDATE_CU) - .putExtra(BroadcastConstants.PROGRESS, 100) - .putExtra(BroadcastConstants.PENDING_TRANSFERS, 0) - ) } } } + /** + * Broadcast progress + * + * @param progress a value between 0 and 100 + * @param pending count of items pending to be uploaded + */ + private suspend fun broadcastProgress(progress: Int, pending: Int) { + broadcastCameraUploadProgress(progress, pending) + } + private fun isCompressorAvailable() = !(videoCompressionJob?.isActive ?: false) private suspend fun startVideoCompression() { @@ -2023,8 +2039,7 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback { } } - @Synchronized - private fun updateProgressNotification() { + private suspend fun updateProgressNotification() { // refresh UI every 1 seconds to avoid too much workload on main thread val now = System.currentTimeMillis() lastUpdated = if (now - lastUpdated > Util.ONTRANSFERUPDATE_REFRESH_MILLIS) { @@ -2053,20 +2068,16 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback { totalTransfers - pendingTransfers + 1 } - sendBroadcast( - Intent(BroadcastConstants.ACTION_UPDATE_CU) - .putExtra(BroadcastConstants.PROGRESS, progressPercent) - .putExtra(BroadcastConstants.PENDING_TRANSFERS, pendingTransfers) - ) + broadcastProgress(progressPercent, pendingTransfers) message = if (megaApi.areTransfersPaused(MegaTransfer.TYPE_UPLOAD)) { - StringResourcesUtils.getString( + getString( R.string.upload_service_notification_paused, inProgress, totalTransfers ) } else { - StringResourcesUtils.getString( + getString( R.string.upload_service_notification, inProgress, totalTransfers @@ -2077,13 +2088,15 @@ class CameraUploadsService : LifecycleService(), OnNetworkTypeChangeCallback { val info = Util.getProgressSize(this, totalSizeTransferred, totalSizePendingTransfer) - showProgressNotification( - progressPercent, - defaultPendingIntent, - message, - info, - getString(R.string.settings_camera_notif_title) - ) + withContext(mainDispatcher) { + showProgressNotification( + progressPercent, + defaultPendingIntent, + message, + info, + getString(R.string.settings_camera_notif_title) + ) + } } private fun createNotification( diff --git a/app/src/main/java/mega/privacy/android/app/presentation/photos/PhotosFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/photos/PhotosFragment.kt index 3d13e995fb4..00e5d9d8c23 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/photos/PhotosFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/photos/PhotosFragment.kt @@ -3,10 +3,7 @@ package mega.privacy.android.app.presentation.photos import android.Manifest.permission.READ_EXTERNAL_STORAGE import android.Manifest.permission.READ_MEDIA_IMAGES import android.Manifest.permission.READ_MEDIA_VIDEO -import android.content.BroadcastReceiver -import android.content.Context import android.content.Intent -import android.content.IntentFilter import android.graphics.Color import android.os.Build import android.os.Bundle @@ -53,7 +50,6 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import mega.privacy.android.app.MegaApplication import mega.privacy.android.app.R -import mega.privacy.android.app.constants.BroadcastConstants import mega.privacy.android.app.extensions.navigateToAppSettings import mega.privacy.android.app.featuretoggle.AppFeatures import mega.privacy.android.app.imageviewer.ImageViewerActivity @@ -87,12 +83,10 @@ import mega.privacy.android.app.presentation.photos.timeline.viewmodel.getCurren import mega.privacy.android.app.presentation.photos.timeline.viewmodel.setCUUploadVideos import mega.privacy.android.app.presentation.photos.timeline.viewmodel.setCUUseCellularConnection import mega.privacy.android.app.presentation.photos.timeline.viewmodel.setCurrentSort -import mega.privacy.android.app.presentation.photos.timeline.viewmodel.setShowProgressBar import mega.privacy.android.app.presentation.photos.timeline.viewmodel.shouldEnableCUPage import mega.privacy.android.app.presentation.photos.timeline.viewmodel.showingFilterPage import mega.privacy.android.app.presentation.photos.timeline.viewmodel.showingSortByDialog import mega.privacy.android.app.presentation.photos.timeline.viewmodel.updateFilterState -import mega.privacy.android.app.presentation.photos.timeline.viewmodel.updateProgress import mega.privacy.android.app.presentation.photos.timeline.viewmodel.zoomIn import mega.privacy.android.app.presentation.photos.timeline.viewmodel.zoomOut import mega.privacy.android.app.presentation.photos.view.PhotosBodyView @@ -111,7 +105,6 @@ import mega.privacy.android.domain.entity.photos.AlbumId import mega.privacy.android.domain.entity.photos.Photo import mega.privacy.android.domain.usecase.GetFeatureFlagValue import mega.privacy.android.domain.usecase.GetThemeMode -import timber.log.Timber import javax.inject.Inject /** @@ -198,7 +191,6 @@ class PhotosFragment : Fragment() { override fun onResume() { timelineViewModel.resetCUButtonAndProgress() albumsViewModel.revalidateInput() - registerCUUpdateReceiver() super.onResume() } @@ -776,40 +768,6 @@ class PhotosFragment : Fragment() { */ fun doesAccountHavePhotos(): Boolean = timelineViewModel.state.value.photos.isNotEmpty() - /** - * Register Camera Upload Broadcast - */ - private fun registerCUUpdateReceiver() { - val filter = IntentFilter(BroadcastConstants.ACTION_UPDATE_CU) - requireContext().registerReceiver(cuUpdateReceiver, filter) - } - - /** - * Camera Upload Broadcast to recieve upload progress and pending file - */ - private val cuUpdateReceiver: BroadcastReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val progress = intent.getIntExtra(BroadcastConstants.PROGRESS, 0) - val pending = intent.getIntExtra(BroadcastConstants.PENDING_TRANSFERS, 0) - - updateProgressBarAndTextUI(progress, pending) - } - } - - private fun updateProgressBarAndTextUI(progress: Int, pending: Int) { - val visible = pending > 0 - if (timelineViewModel.state.value.selectedPhotoCount > 0 || !timelineViewModel.isInAllView()) { - timelineViewModel.setShowProgressBar(false) - } else { - // Check to avoid keeping setting same visibility - timelineViewModel.updateProgress( - pending, visible, progress.toFloat() / 100 - ) - - Timber.d("CU Upload Progress: Pending: {$pending}, Progress: {$progress}") - } - } - private fun deleteAlbums(albumIds: List) { albumsViewModel.deleteAlbums(albumIds) @@ -830,7 +788,6 @@ class PhotosFragment : Fragment() { } override fun onDestroy() { - requireContext().unregisterReceiver(cuUpdateReceiver) super.onDestroy() } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/photos/timeline/viewmodel/TimelineViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/photos/timeline/viewmodel/TimelineViewModel.kt index 9f9fd4e61cf..4e96e3bca96 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/photos/timeline/viewmodel/TimelineViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/photos/timeline/viewmodel/TimelineViewModel.kt @@ -41,6 +41,7 @@ import mega.privacy.android.domain.usecase.FilterCameraUploadPhotos import mega.privacy.android.domain.usecase.FilterCloudDrivePhotos import mega.privacy.android.domain.usecase.GetTimelinePhotos import mega.privacy.android.domain.usecase.IsCameraSyncPreferenceEnabled +import mega.privacy.android.domain.usecase.MonitorCameraUploadProgress import mega.privacy.android.domain.usecase.SetInitialCUPreferences import nz.mega.sdk.MegaNode import org.jetbrains.anko.collections.forEachWithIndex @@ -63,6 +64,7 @@ import javax.inject.Inject * @property ioDispatcher * @property mainDispatcher * @property checkEnableCameraUploadsStatus + * @param monitorCameraUploadProgress */ @HiltViewModel class TimelineViewModel @Inject constructor( @@ -77,6 +79,7 @@ class TimelineViewModel @Inject constructor( @IoDispatcher val ioDispatcher: CoroutineDispatcher, @MainDispatcher val mainDispatcher: CoroutineDispatcher, private val checkEnableCameraUploadsStatus: CheckEnableCameraUploadsStatus, + monitorCameraUploadProgress: MonitorCameraUploadProgress, ) : ViewModel() { internal val _state = MutableStateFlow(TimelineViewState(loadPhotosDone = false)) @@ -100,6 +103,35 @@ class TimelineViewModel @Inject constructor( ) } } + viewModelScope.launch { + monitorCameraUploadProgress().collectLatest { + updateCameraUploadProgressIfNeeded(progress = it.first, pending = it.second) + } + } + } + + /** + * Update the camera upload progress if needed + * + * The progress is set to the state only if no photos are currently selected or + * the current view is not [TimeBarTab.All] + * + * @param progress value between 0 and 100 + * @param pending count of pending items to be uploaded + */ + private fun updateCameraUploadProgressIfNeeded(progress: Int, pending: Int) { + if (state.value.selectedPhotoCount > 0 || !isInAllView()) { + setShowProgressBar(show = false) + } else { + // Check to avoid keeping setting same visibility + updateProgress( + pending = pending, + showProgress = pending > 0, + progress = progress.toFloat() / 100 + ) + + Timber.d("CU Upload Progress: Pending: {$pending}, Progress: {$progress}") + } } /** diff --git a/app/src/test/java/test/mega/privacy/android/app/di/TestCameraUploadUseCases.kt b/app/src/test/java/test/mega/privacy/android/app/di/TestCameraUploadUseCases.kt index fbc3772eaa6..5517e273141 100644 --- a/app/src/test/java/test/mega/privacy/android/app/di/TestCameraUploadUseCases.kt +++ b/app/src/test/java/test/mega/privacy/android/app/di/TestCameraUploadUseCases.kt @@ -29,6 +29,7 @@ import mega.privacy.android.app.domain.usecase.ProcessMediaForUpload import mega.privacy.android.app.domain.usecase.SaveSyncRecordsToDB import mega.privacy.android.app.domain.usecase.SetOriginalFingerprint import mega.privacy.android.domain.usecase.BackupTimeStampsAndFolderHandle +import mega.privacy.android.domain.usecase.BroadcastCameraUploadProgress import mega.privacy.android.domain.usecase.BroadcastUploadPauseState import mega.privacy.android.domain.usecase.CheckEnableCameraUploadsStatus import mega.privacy.android.domain.usecase.ClearCacheDirectory @@ -66,6 +67,7 @@ import mega.privacy.android.domain.usecase.KeepFileNames import mega.privacy.android.domain.usecase.MediaLocalPathExists import mega.privacy.android.domain.usecase.MonitorBatteryInfo import mega.privacy.android.domain.usecase.MonitorCameraUploadPauseState +import mega.privacy.android.domain.usecase.MonitorCameraUploadProgress import mega.privacy.android.domain.usecase.MonitorChargingStoppedState import mega.privacy.android.domain.usecase.RenamePrimaryFolder import mega.privacy.android.domain.usecase.RenameSecondaryFolder @@ -396,4 +398,12 @@ object TestCameraUploadUseCases { @Provides fun provideListenToNewMedia() = mock() + + @Provides + fun provideBroadcastCameraUploadProgress() = + mock() + + @Provides + fun provideMonitorCameraUploadsProgress() = + mock() } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/photos/timeline/viewmodel/TimelineViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/photos/timeline/viewmodel/TimelineViewModelTest.kt index dd5e00d62a5..20cbd743832 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/photos/timeline/viewmodel/TimelineViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/photos/timeline/viewmodel/TimelineViewModelTest.kt @@ -15,14 +15,14 @@ import kotlinx.coroutines.test.resetMain import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.setMain import mega.privacy.android.app.domain.usecase.GetNodeListByIds -import mega.privacy.android.app.presentation.photos.timeline.model.ApplyFilterMediaType import mega.privacy.android.app.presentation.photos.model.DateCard import mega.privacy.android.app.presentation.photos.model.FilterMediaType -import mega.privacy.android.app.presentation.photos.timeline.model.PhotoListItem import mega.privacy.android.app.presentation.photos.model.Sort import mega.privacy.android.app.presentation.photos.model.TimeBarTab -import mega.privacy.android.app.presentation.photos.timeline.model.TimelinePhotosSource import mega.privacy.android.app.presentation.photos.model.ZoomLevel +import mega.privacy.android.app.presentation.photos.timeline.model.ApplyFilterMediaType +import mega.privacy.android.app.presentation.photos.timeline.model.PhotoListItem +import mega.privacy.android.app.presentation.photos.timeline.model.TimelinePhotosSource import mega.privacy.android.app.presentation.photos.timeline.viewmodel.TimelineViewModel import mega.privacy.android.data.wrapper.JobUtilWrapper import mega.privacy.android.domain.entity.account.EnableCameraUploadsStatus @@ -33,6 +33,7 @@ import mega.privacy.android.domain.usecase.FilterCameraUploadPhotos import mega.privacy.android.domain.usecase.FilterCloudDrivePhotos import mega.privacy.android.domain.usecase.GetTimelinePhotos import mega.privacy.android.domain.usecase.IsCameraSyncPreferenceEnabled +import mega.privacy.android.domain.usecase.MonitorCameraUploadProgress import mega.privacy.android.domain.usecase.SetInitialCUPreferences import org.junit.After import org.junit.Before @@ -73,6 +74,8 @@ class TimelineViewModelTest { private val checkEnableCameraUploadsStatus = mock() + private val monitorCameraUploadProgress = mock() + @Before fun setUp() { Dispatchers.setMain(StandardTestDispatcher()) @@ -88,6 +91,7 @@ class TimelineViewModelTest { ioDispatcher = StandardTestDispatcher(), mainDispatcher = StandardTestDispatcher(), checkEnableCameraUploadsStatus = checkEnableCameraUploadsStatus, + monitorCameraUploadProgress = monitorCameraUploadProgress, ) } @@ -253,4 +257,72 @@ class TimelineViewModelTest { assertThat(state.shouldTriggerCameraUploads).isTrue() } } -} \ No newline at end of file + + @Test + fun `test that when camera upload progress is received, then state is set properly`() = + runTest { + val expectedProgress = 50 + val expectedPending = 25 + val pair = Pair(expectedProgress, expectedPending) + + whenever(monitorCameraUploadProgress()).thenReturn(flowOf(pair)) + + advanceUntilIdle() + + underTest.state.test { + val state = awaitItem() + assertThat(state.pending).isEqualTo(expectedPending) + assertThat(state.progressBarShowing).isEqualTo(true) + assertThat(state.progress).isEqualTo(expectedProgress.toFloat() / 100) + } + } + + @Test + fun `test that when camera upload progress is received with pending 0, then progressBarShowing state is set to false`() = + runTest { + val expectedProgress = 50 + val expectedPending = 0 + val pair = Pair(expectedProgress, expectedPending) + + whenever(monitorCameraUploadProgress()).thenReturn(flowOf(pair)) + + advanceUntilIdle() + + underTest.state.test { + val state = awaitItem() + assertThat(state.progressBarShowing).isEqualTo(false) + } + } + + @Test + fun `test that when camera upload progress is received and some items are currently selected, the progressBarShowing state is set to false`() = + runTest { + val progress = flowOf(mock>()) + + underTest.setSelectedPhotos(listOf(mock())) + whenever(monitorCameraUploadProgress()).thenReturn(progress) + + advanceUntilIdle() + + underTest.state.test { + val state = awaitItem() + assertThat(state.progressBarShowing).isEqualTo(false) + } + } + + @Test + fun `test that when camera upload progress is received and current view is not TimeBar ALL, the progressBarShowing state is set to false`() = + runTest { + val progress = flowOf(mock>()) + + underTest.onTimeBarTabSelected(TimeBarTab.Years) + whenever(monitorCameraUploadProgress()).thenReturn(progress) + + advanceUntilIdle() + + underTest.state.test { + val state = awaitItem() + assertThat(state.progressBarShowing).isEqualTo(false) + } + } +} From dcf22ff6a1a6c229e3f8a1c37dc44ae3663aa593 Mon Sep 17 00:00:00 2001 From: Raquel Garcia Chico Date: Mon, 13 Mar 2023 08:53:35 +1300 Subject: [PATCH 231/334] MEET-1208 AND - user starts or joins a scheduled meeting --- .../mega/privacy/android/app/BaseActivity.kt | 12 +++ .../app/main/megachat/ChatActivity.java | 83 +++++++++---------- .../MeetingListBottomSheetDialogFragment.kt | 74 +++++++++-------- .../app/meeting/list/MeetingListFragment.kt | 37 +++++++-- .../app/meeting/list/MeetingListViewModel.kt | 7 ++ .../app/presentation/chat/ChatViewModel.kt | 31 ++++--- .../meeting/model/MeetingListState.kt | 2 + .../privacy/android/app/utils/Constants.java | 2 + .../app/utils/permission/PermissionUtils.kt | 21 ++++- .../domain/entity/chat/MeetingRoomItem.kt | 2 +- 10 files changed, 173 insertions(+), 98 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt index c4568a071eb..d477883de81 100644 --- a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt @@ -90,6 +90,7 @@ import mega.privacy.android.app.utils.Constants.INVITE_CONTACT_TYPE import mega.privacy.android.app.utils.Constants.LOGIN_FRAGMENT import mega.privacy.android.app.utils.Constants.MESSAGE_SNACKBAR_TYPE import mega.privacy.android.app.utils.Constants.MUTE_NOTIFICATIONS_SNACKBAR_TYPE +import mega.privacy.android.app.utils.Constants.NOT_CALL_PERMISSIONS_SNACKBAR_TYPE import mega.privacy.android.app.utils.Constants.NOT_SPACE_SNACKBAR_TYPE import mega.privacy.android.app.utils.Constants.OPEN_FILE_SNACKBAR_TYPE import mega.privacy.android.app.utils.Constants.PERMISSIONS_TYPE @@ -894,6 +895,11 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque s ?: return, Snackbar.LENGTH_INDEFINITE ) + NOT_CALL_PERMISSIONS_SNACKBAR_TYPE -> Snackbar.make( + view, + s ?: return, + Snackbar.LENGTH_LONG + ) else -> Snackbar.make(view, s ?: return, Snackbar.LENGTH_LONG) } } catch (e: Exception) { @@ -967,6 +973,12 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque ) show() } + NOT_CALL_PERMISSIONS_SNACKBAR_TYPE -> { + setAction( + R.string.general_allow, toAppInfo(applicationContext) + ) + show() + } } } } diff --git a/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java b/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java index 207da92ccc7..1f5100887f5 100644 --- a/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java @@ -48,7 +48,6 @@ import static mega.privacy.android.app.utils.CacheFolderManager.buildVoiceClipFile; import static mega.privacy.android.app.utils.CallUtil.activateChrono; import static mega.privacy.android.app.utils.CallUtil.callStatusToString; -import static mega.privacy.android.app.utils.CallUtil.canCallBeStartedFromContactOption; import static mega.privacy.android.app.utils.CallUtil.checkIfCanJoinOneToOneCall; import static mega.privacy.android.app.utils.CallUtil.getAnotherCallOnHold; import static mega.privacy.android.app.utils.CallUtil.getCallInProgress; @@ -155,8 +154,6 @@ import static mega.privacy.android.app.utils.Constants.REACTION_ERROR_TYPE_USER; import static mega.privacy.android.app.utils.Constants.RECORD_VOICE_CLIP; import static mega.privacy.android.app.utils.Constants.REQUEST_ADD_PARTICIPANTS; -import static mega.privacy.android.app.utils.Constants.REQUEST_BT_CONNECT; -import static mega.privacy.android.app.utils.Constants.REQUEST_CAMERA; import static mega.privacy.android.app.utils.Constants.REQUEST_CAMERA_SHOW_PREVIEW; import static mega.privacy.android.app.utils.Constants.REQUEST_CAMERA_TAKE_PICTURE; import static mega.privacy.android.app.utils.Constants.REQUEST_CODE_GET_FILES; @@ -165,7 +162,6 @@ import static mega.privacy.android.app.utils.Constants.REQUEST_CODE_SELECT_IMPORT_FOLDER; import static mega.privacy.android.app.utils.Constants.REQUEST_CODE_SEND_LOCATION; import static mega.privacy.android.app.utils.Constants.REQUEST_READ_STORAGE; -import static mega.privacy.android.app.utils.Constants.REQUEST_RECORD_AUDIO; import static mega.privacy.android.app.utils.Constants.REQUEST_SEND_CONTACTS; import static mega.privacy.android.app.utils.Constants.REQUEST_STORAGE_VOICE_CLIP; import static mega.privacy.android.app.utils.Constants.REQUEST_WRITE_STORAGE_TAKE_PICTURE; @@ -205,7 +201,9 @@ import static mega.privacy.android.app.utils.Util.scaleHeightPx; import static mega.privacy.android.app.utils.Util.showErrorAlertDialog; import static mega.privacy.android.app.utils.Util.toCDATA; +import static mega.privacy.android.app.utils.permission.PermissionUtils.checkMandatoryCallPermissions; import static mega.privacy.android.app.utils.permission.PermissionUtils.hasPermissions; +import static mega.privacy.android.app.utils.permission.PermissionUtils.requestCallPermissions; import static mega.privacy.android.app.utils.permission.PermissionUtils.requestPermission; import static mega.privacy.android.data.facade.FileFacadeKt.INTENT_EXTRA_NODE_HANDLE; import static nz.mega.sdk.MegaApiJava.INVALID_HANDLE; @@ -388,6 +386,7 @@ import mega.privacy.android.app.presentation.chat.ChatViewModel; import mega.privacy.android.app.presentation.chat.dialog.AddParticipantsNoContactsDialogFragment; import mega.privacy.android.app.presentation.chat.dialog.AddParticipantsNoContactsLeftToAddDialogFragment; +import mega.privacy.android.app.presentation.extensions.StorageStateExtensionsKt; import mega.privacy.android.app.presentation.folderlink.FolderLinkActivity; import mega.privacy.android.app.presentation.login.LoginActivity; import mega.privacy.android.app.usecase.CopyNodeUseCase; @@ -405,6 +404,7 @@ import mega.privacy.android.app.utils.CallUtil; import mega.privacy.android.app.utils.ChatUtil; import mega.privacy.android.app.utils.ColorUtils; +import mega.privacy.android.app.utils.Constants; import mega.privacy.android.app.utils.ContactUtil; import mega.privacy.android.app.utils.FileUtil; import mega.privacy.android.app.utils.MegaProgressDialogUtil; @@ -815,6 +815,8 @@ public class ChatActivity extends PasscodeActivity private ActivityResultLauncher scanDocumentLauncher = null; private ActivityResultLauncher takePictureLauncher = null; + private ActivityResultLauncher permissionsRequest = null; + /** * Current contact online status. */ @@ -863,6 +865,7 @@ public void setExportListener(ExportListener exportListener) { case MegaChatCall.CALL_STATUS_USER_NO_PRESENT: case MegaChatCall.CALL_STATUS_IN_PROGRESS: case MegaChatCall.CALL_STATUS_DESTROYED: + updateCallBanner(); if (call.getStatus() == MegaChatCall.CALL_STATUS_IN_PROGRESS) { cancelRecording(); @@ -1209,9 +1212,7 @@ public void onStartCallOptionClicked(boolean videoOn) { startVideo = videoOn; - if (checkPermissionsCall()) { - startCall(); - } + startCall(); } @Override @@ -1499,7 +1500,6 @@ public void showGroupOrContactInfoActivity() { public void onCreate(Bundle savedInstanceState) { supportRequestWindowFeature(Window.FEATURE_NO_TITLE); super.onCreate(savedInstanceState); - viewModel = new ViewModelProvider(this).get(ChatViewModel.class); if (shouldRefreshSessionDueToKarere()) { @@ -1590,6 +1590,21 @@ public void onCreate(Bundle savedInstanceState) { mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); + permissionsRequest = registerForActivityResult( + new ActivityResultContracts.RequestMultiplePermissions(), + result -> { + if (checkMandatoryCallPermissions(chatActivity)) { + enableCallMenuItems(false); + viewModel.onCallTap(startVideo); + } else { + showSnackbar(Constants.NOT_CALL_PERMISSIONS_SNACKBAR_TYPE, + fragmentContainer, + getString(R.string.allow_acces_calls_subtitle_microphone), + MegaChatApiJava.MEGACHAT_INVALID_HANDLE + ); + } + }); + sendGifLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { @@ -2067,18 +2082,23 @@ private void collectFlows() { } ScheduledMeetingStatus schedMeetStatus = chatState.getScheduledMeetingStatus(); + if (chatRoom.isActive() && !chatRoom.isArchived() && schedMeetStatus != null && (schedMeetStatus == ScheduledMeetingStatus.NotStarted || schedMeetStatus == ScheduledMeetingStatus.NotJoined)) { - startOrJoinMeetingBanner.setText(schedMeetStatus == ScheduledMeetingStatus.NotStarted ? R.string.meetings_chat_room_start_scheduled_meeting_option : R.string.meetings_chat_room_join_scheduled_meeting_option); + startOrJoinMeetingBanner.setVisibility(View.VISIBLE); callInProgressLayout.setVisibility(View.GONE); } else { startOrJoinMeetingBanner.setVisibility(View.GONE); } + if (chatState.getSchedId() != null) { + updateCallBanner(); + } + long callChatId = chatState.getCurrentCallChatId(); if (callChatId != MEGACHAT_INVALID_HANDLE) { @@ -3692,7 +3712,12 @@ private void checkCallInThisChat() { return; } - if (!participatingInACall() && canCallBeStartedFromContactOption(this, passcodeManagement)) { + if(StorageStateExtensionsKt.getStorageState() == StorageState.PayWall) { + showOverDiskQuotaPaywallWarning(); + return; + } + + if (!participatingInACall()) { Timber.d("There is not a call in this chat and I am NOT in another call"); startCall(); } @@ -3702,8 +3727,7 @@ private void checkCallInThisChat() { * Start a call */ private void startCall() { - enableCallMenuItems(false); - viewModel.onCallTap(startVideo); + requestCallPermissions(permissionsRequest); } private void enableCallMenuItems(Boolean enable) { @@ -3784,18 +3808,6 @@ private boolean checkPermissionsVoiceClip() { return checkPermissions(RECORD_VOICE_CLIP, Manifest.permission.RECORD_AUDIO); } - private boolean checkPermissionsCall() { - Timber.d("checkPermissionsCall"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - return checkPermissions(REQUEST_CAMERA, Manifest.permission.CAMERA) - && checkPermissions(REQUEST_RECORD_AUDIO, Manifest.permission.RECORD_AUDIO) - && checkPermissions(REQUEST_BT_CONNECT, Manifest.permission.BLUETOOTH_CONNECT); - } else { - return checkPermissions(REQUEST_CAMERA, Manifest.permission.CAMERA) - && checkPermissions(REQUEST_RECORD_AUDIO, Manifest.permission.RECORD_AUDIO); - } - } - private boolean checkPermissionsTakePicture() { Timber.d("checkPermissionsTakePicture"); return checkPermissions(REQUEST_CAMERA_TAKE_PICTURE, Manifest.permission.CAMERA) @@ -3829,18 +3841,6 @@ public void onRequestPermissionsResult(int requestCode, String[] permissions, in } switch (requestCode) { - case REQUEST_RECORD_AUDIO: - if (grantResults[0] == PackageManager.PERMISSION_GRANTED && checkPermissionsCall()) { - startCall(); - } - break; - - case REQUEST_CAMERA: - if(checkPermissionsCall()) { - startCall(); - } - break; - case REQUEST_CAMERA_TAKE_PICTURE: case REQUEST_WRITE_STORAGE_TAKE_PICTURE: if (grantResults[0] == PackageManager.PERMISSION_GRANTED && checkPermissionsTakePicture()) { @@ -4596,10 +4596,8 @@ public void onClick(View v) { break; case R.id.start_or_join_meeting_banner: - if (checkPermissionsCall()) { - startVideo = false; - startCall(); - } + startVideo = false; + startCall(); break; } @@ -8970,7 +8968,6 @@ public void onResume() { } activityVisible = true; - updateCallBanner(); if (aB != null && aB.getTitle() != null) { titleToolbar.setText(titleToolbar.getText()); } @@ -9237,7 +9234,9 @@ private void showCallInProgressLayout(String text, boolean shouldChronoShown, Me chatIdBanner = call.getChatid(); - if (callInProgressLayout != null && callInProgressLayout.getVisibility() != View.VISIBLE && + if (!chatRoom.isArchived() && chatRoom.isActive() && callInProgressLayout != null && + callInProgressLayout.getVisibility() != View.VISIBLE && + viewModel.getState().getValue().getSchedId() != null && startOrJoinMeetingBanner.getVisibility() != View.VISIBLE) { callInProgressLayout.setAlpha(1); callInProgressLayout.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListBottomSheetDialogFragment.kt b/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListBottomSheetDialogFragment.kt index d7d86e14acb..3535b48f1f4 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListBottomSheetDialogFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListBottomSheetDialogFragment.kt @@ -29,7 +29,7 @@ import mega.privacy.android.app.presentation.meeting.RecurringMeetingInfoActivit import mega.privacy.android.app.presentation.meeting.ScheduledMeetingInfoActivity import mega.privacy.android.app.utils.ChatUtil import mega.privacy.android.app.utils.Constants -import mega.privacy.android.app.utils.permission.PermissionUtils.checkCallPermissions +import mega.privacy.android.app.utils.permission.PermissionUtils.checkMandatoryCallPermissions import mega.privacy.android.app.utils.permission.PermissionUtils.requestCallPermissions import mega.privacy.android.app.utils.setImageRequestFromFilePath import mega.privacy.android.app.utils.view.TextDrawable @@ -85,7 +85,6 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { @SuppressLint("RestrictedApi") height = dpToPx(requireContext(), 71).toInt() } - permissionsRequest = getCallPermissionsRequest() return binding.root } @@ -95,22 +94,27 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { */ private fun getCallPermissionsRequest() = registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { - if (checkCallPermissions(requireActivity())) { + if (checkMandatoryCallPermissions(requireActivity())) { currentMeeting?.let { room -> - when (room.scheduledMeetingStatus) { - ScheduledMeetingStatus.NotStarted -> viewModel.startSchedMeeting( - room.chatId, - room.schedId - ) - ScheduledMeetingStatus.NotJoined -> viewModel.joinSchedMeeting( - room.chatId - ) - else -> {} + room.scheduledMeetingStatus?.let { scheduledMeetingStatus -> + when (scheduledMeetingStatus) { + ScheduledMeetingStatus.NotStarted -> viewModel.startSchedMeeting( + room.chatId, + room.schedId + ) + ScheduledMeetingStatus.NotJoined -> viewModel.joinSchedMeeting( + room.chatId + ) + else -> {} + } } } - dismissAllowingStateLoss() + } else { + viewModel.updateSnackBar(R.string.allow_acces_calls_subtitle_microphone) } + + dismissAllowingStateLoss() } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -120,6 +124,8 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { Lifecycle.State.RESUMED, ::showMeeting ) + + permissionsRequest = getCallPermissionsRequest() } private fun showMeeting(room: MeetingRoomItem?) { @@ -185,31 +191,33 @@ class MeetingListBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { } binding.btnStartOrJoinSchedMeeting.isVisible = - room.isActive && + room.isActive && room.scheduledMeetingStatus != null && room.scheduledMeetingStatus != ScheduledMeetingStatus.Joined binding.dividerStartOrJoinSchedMeeting.isVisible = binding.btnStartOrJoinSchedMeeting.isVisible - when (room.scheduledMeetingStatus) { - ScheduledMeetingStatus.NotStarted -> { - binding.btnStartOrJoinSchedMeeting.setText(R.string.meetings_list_start_scheduled_meeting_option) - binding.btnStartOrJoinSchedMeeting.setCompoundDrawablesRelativeWithIntrinsicBounds( - R.drawable.start_sched_icon, - 0, - 0, - 0 - ) - } - ScheduledMeetingStatus.NotJoined -> { - binding.btnStartOrJoinSchedMeeting.setText(R.string.meetings_list_join_scheduled_meeting_option) - binding.btnStartOrJoinSchedMeeting.setCompoundDrawablesRelativeWithIntrinsicBounds( - R.drawable.join_sched_icon, - 0, - 0, - 0 - ) + room.scheduledMeetingStatus?.let { scheduledMeetingStatus -> + when (scheduledMeetingStatus) { + ScheduledMeetingStatus.NotStarted -> { + binding.btnStartOrJoinSchedMeeting.setText(R.string.meetings_list_start_scheduled_meeting_option) + binding.btnStartOrJoinSchedMeeting.setCompoundDrawablesRelativeWithIntrinsicBounds( + R.drawable.start_sched_icon, + 0, + 0, + 0 + ) + } + ScheduledMeetingStatus.NotJoined -> { + binding.btnStartOrJoinSchedMeeting.setText(R.string.meetings_list_join_scheduled_meeting_option) + binding.btnStartOrJoinSchedMeeting.setCompoundDrawablesRelativeWithIntrinsicBounds( + R.drawable.join_sched_icon, + 0, + 0, + 0 + ) + } + else -> {} } - else -> {} } binding.btnStartOrJoinSchedMeeting.setOnClickListener { diff --git a/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListFragment.kt b/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListFragment.kt index 7d43362c511..27019c21f02 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListFragment.kt @@ -37,8 +37,8 @@ import mega.privacy.android.app.meeting.list.adapter.MeetingsAdapter import mega.privacy.android.app.modalbottomsheet.MeetingBottomSheetDialogFragment import mega.privacy.android.app.utils.ChatUtil import mega.privacy.android.app.utils.Constants -import mega.privacy.android.app.utils.StringResourcesUtils import mega.privacy.android.app.utils.Util +import nz.mega.sdk.MegaChatApiJava @AndroidEntryPoint class MeetingListFragment : Fragment() { @@ -178,6 +178,16 @@ class MeetingListFragment : Fragment() { viewModel.removeCurrentCall() launchChatCallScreen(state.currentCallChatId) } + + state.snackBar?.let { + (requireActivity() as ManagerActivity).showSnackbar( + Constants.NOT_CALL_PERMISSIONS_SNACKBAR_TYPE, + binding.root, + getString(it), + MegaChatApiJava.MEGACHAT_INVALID_HANDLE + ) + viewModel.updateSnackBar(null) + } } } @@ -289,26 +299,35 @@ class MeetingListFragment : Fragment() { .controlMuteNotificationsOfAChat( requireContext(), Constants.NOTIFICATIONS_ENABLED, - id) + id + ) } clearSelections() true } R.id.cab_menu_archive -> { - val chatsToArchive = meetingsAdapter.tracker?.selection?.toList() ?: return true + val chatsToArchive = + meetingsAdapter.tracker?.selection?.toList() ?: return true viewModel.archiveChats(chatsToArchive) clearSelections() true } R.id.chat_list_leave_chat_layout -> { - val chatsToLeave = meetingsAdapter.tracker?.selection?.toList() ?: return true - MaterialAlertDialogBuilder(requireContext(), R.style.ThemeOverlay_Mega_MaterialAlertDialog) - .setTitle(StringResourcesUtils.getString(R.string.title_confirmation_leave_group_chat)) - .setMessage(StringResourcesUtils.getString(R.string.confirmation_leave_group_chat)) - .setPositiveButton(StringResourcesUtils.getString(R.string.general_leave)) { _, _ -> + val chatsToLeave = + meetingsAdapter.tracker?.selection?.toList() ?: return true + MaterialAlertDialogBuilder( + requireContext(), + R.style.ThemeOverlay_Mega_MaterialAlertDialog + ) + .setTitle(getString(R.string.title_confirmation_leave_group_chat)) + .setMessage(getString(R.string.confirmation_leave_group_chat)) + .setPositiveButton(getString(R.string.general_leave)) { _, _ -> viewModel.leaveChats(chatsToLeave) } - .setNegativeButton(StringResourcesUtils.getString(R.string.general_cancel), null) + .setNegativeButton( + getString(R.string.general_cancel), + null + ) .show() clearSelections() true diff --git a/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListViewModel.kt b/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListViewModel.kt index 3931fe15376..0ea036ca31f 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/list/MeetingListViewModel.kt @@ -255,6 +255,13 @@ class MeetingListViewModel @Inject constructor( state.update { it.copy(currentCallChatId = null) } } + /** + * Update snackBar + */ + fun updateSnackBar(text: Int?) { + state.update { it.copy(snackBar = text) } + } + /** * Archive chat * diff --git a/app/src/main/java/mega/privacy/android/app/presentation/chat/ChatViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/chat/ChatViewModel.kt index 02a60ed81fa..b35c29d95a3 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/chat/ChatViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/chat/ChatViewModel.kt @@ -167,22 +167,19 @@ class ChatViewModel @Inject constructor( getScheduledMeetingByChat(state.value.chatId) }.onFailure { Timber.d("Scheduled meeting does not exist") + _state.update { + it.copy( + schedId = megaChatApiGateway.getChatInvalidHandle(), + scheduledMeetingStatus = null + ) + } }.onSuccess { scheduledMeetingList -> scheduledMeetingList?.let { list -> list.forEach { scheduledMeetReceived -> if (scheduledMeetReceived.parentSchedId == chatApiGateway.getChatInvalidHandle()) { - _state.update { - it.copy( - schedId = scheduledMeetReceived.schedId, - schedIsPending = !scheduledMeetReceived.isPast() - ) - } - var scheduledMeetingStatus: ScheduledMeetingStatus? = null - getChatCallUpdates() - + var scheduledMeetingStatus = ScheduledMeetingStatus.NotStarted if (!scheduledMeetReceived.isPast()) { Timber.d("Has scheduled meeting") - scheduledMeetingStatus = ScheduledMeetingStatus.NotStarted getChatCall(scheduledMeetReceived.chatId)?.let { call -> when (call.status) { ChatCallStatus.UserNoPresent -> scheduledMeetingStatus = @@ -197,12 +194,24 @@ class ChatViewModel @Inject constructor( } _state.update { it.copy( + schedId = scheduledMeetReceived.schedId, + schedIsPending = !scheduledMeetReceived.isPast(), scheduledMeetingStatus = scheduledMeetingStatus ) } + return@forEach } + } + } + + getChatCallUpdates() - return@forEach + if (_state.value.schedId == null) { + _state.update { + it.copy( + schedId = megaChatApiGateway.getChatInvalidHandle(), + scheduledMeetingStatus = null + ) } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/meeting/model/MeetingListState.kt b/app/src/main/java/mega/privacy/android/app/presentation/meeting/model/MeetingListState.kt index 2b68ca26f23..4bb29e9f92b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/meeting/model/MeetingListState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/meeting/model/MeetingListState.kt @@ -7,8 +7,10 @@ import mega.privacy.android.domain.entity.chat.MeetingRoomItem * * @property meetings * @property currentCallChatId + * @property snackBar String resource id for showing an snackBar. */ data class MeetingListState constructor( val meetings: List = listOf(), val currentCallChatId: Long? = null, + val snackBar: Int? = null, ) diff --git a/app/src/main/java/mega/privacy/android/app/utils/Constants.java b/app/src/main/java/mega/privacy/android/app/utils/Constants.java index dc06f4e4462..27b184bd65c 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/Constants.java +++ b/app/src/main/java/mega/privacy/android/app/utils/Constants.java @@ -564,6 +564,8 @@ public class Constants { public static final int SENT_REQUESTS_TYPE = 8; public static final int RESUME_TRANSFERS_TYPE = 9; + public static final int NOT_CALL_PERMISSIONS_SNACKBAR_TYPE = 10; + public static final int INFO_ANIMATION = 3000; public static final int QUICK_INFO_ANIMATION = 500; diff --git a/app/src/main/java/mega/privacy/android/app/utils/permission/PermissionUtils.kt b/app/src/main/java/mega/privacy/android/app/utils/permission/PermissionUtils.kt index 72575aa06b2..b3eab6d612b 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/permission/PermissionUtils.kt +++ b/app/src/main/java/mega/privacy/android/app/utils/permission/PermissionUtils.kt @@ -242,8 +242,8 @@ object PermissionUtils { * @return True, if permissions are granted. False, if not. */ @JvmStatic - fun checkCallPermissions(activity: Activity): Boolean = - hasPermissions(activity, *getListCallPermissionsByVersion()) + fun checkMandatoryCallPermissions(activity: Activity): Boolean = + hasPermissions(activity, *getMandatoryListCallPermissionsByVersion()) /** * Ask for call permissions. @@ -273,6 +273,23 @@ object PermissionUtils { ) } + /** + * Get list of call permissions by version + * + * @return List of required permissions + */ + private fun getMandatoryListCallPermissionsByVersion() = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + arrayOf( + getBluetoothConnectPermission(), + getRecordAudioPermission() + ) + } else { + arrayOf( + getRecordAudioPermission(), + ) + } + /** * Displays the Notification Permission Rationale * diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/entity/chat/MeetingRoomItem.kt b/domain/src/main/kotlin/mega/privacy/android/domain/entity/chat/MeetingRoomItem.kt index 1822057cd7e..9d77324042b 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/entity/chat/MeetingRoomItem.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/entity/chat/MeetingRoomItem.kt @@ -62,7 +62,7 @@ data class MeetingRoomItem constructor( val scheduledStartTimestamp: Long? = null, val scheduledEndTimestamp: Long? = null, val scheduledTimestampFormatted: String? = null, - val scheduledMeetingStatus: ScheduledMeetingStatus = ScheduledMeetingStatus.NotStarted, + val scheduledMeetingStatus: ScheduledMeetingStatus? = null, ) { fun isSingleMeeting(): Boolean = From 9832598a1c1e24817622a1ff7d2158579c26fd40 Mon Sep 17 00:00:00 2001 From: Yenel Date: Mon, 13 Mar 2023 11:17:08 +0100 Subject: [PATCH 232/334] AND-15920 Crash sharing a device PDF from MEGA to MEGA --- .../android/app/listeners/GlobalListener.kt | 4 ++- .../OptionalMegaGlobalListenerInterface.kt | 4 +-- .../android/app/main/ContactInfoActivity.kt | 28 +++++++++---------- .../android/app/main/FileExplorerActivity.kt | 10 +++---- .../android/app/main/PdfViewerActivity.kt | 14 +++++----- .../android/app/main/VersionsFileActivity.kt | 12 ++++---- 6 files changed, 37 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/listeners/GlobalListener.kt b/app/src/main/java/mega/privacy/android/app/listeners/GlobalListener.kt index 3044016dc24..e9bacb66545 100644 --- a/app/src/main/java/mega/privacy/android/app/listeners/GlobalListener.kt +++ b/app/src/main/java/mega/privacy/android/app/listeners/GlobalListener.kt @@ -196,7 +196,9 @@ class GlobalListener @Inject constructor( } } - override fun onEvent(api: MegaApiJava, event: MegaEvent) { + override fun onEvent(api: MegaApiJava, event: MegaEvent?) { + if (event == null) return + Timber.d("Event received: text(${event.text}), type(${event.type}), number(${event.number})") when (event.type) { diff --git a/app/src/main/java/mega/privacy/android/app/listeners/OptionalMegaGlobalListenerInterface.kt b/app/src/main/java/mega/privacy/android/app/listeners/OptionalMegaGlobalListenerInterface.kt index ce0170a9982..25b16de1c6c 100644 --- a/app/src/main/java/mega/privacy/android/app/listeners/OptionalMegaGlobalListenerInterface.kt +++ b/app/src/main/java/mega/privacy/android/app/listeners/OptionalMegaGlobalListenerInterface.kt @@ -44,8 +44,8 @@ class OptionalMegaGlobalListenerInterface( onContactRequestsUpdate?.invoke(requests) } - override fun onEvent(api: MegaApiJava, event: MegaEvent) { - onEvent?.invoke(event) + override fun onEvent(api: MegaApiJava, event: MegaEvent?) { + event?.let { onEvent?.invoke(event) } } override fun onSetsUpdate(api: MegaApiJava?, sets: ArrayList?) { diff --git a/app/src/main/java/mega/privacy/android/app/main/ContactInfoActivity.kt b/app/src/main/java/mega/privacy/android/app/main/ContactInfoActivity.kt index 63d252b6a45..4b0bf612042 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ContactInfoActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/ContactInfoActivity.kt @@ -294,23 +294,23 @@ class ContactInfoActivity : BaseActivity(), ActionNodeCallback, MegaRequestListe } private val megaGlobalListenerInterface = object : MegaGlobalListenerInterface { - override fun onUsersUpdate(api: MegaApiJava, users: ArrayList) { - if (users.isNotEmpty()) { - for (updatedUser in users) { - if (updatedUser.handle == user?.handle) { - user = updatedUser - contentContactProperties.emailText.text = updatedUser.email - break - } + override fun onUsersUpdate(api: MegaApiJava, users: ArrayList?) { + if (users.isNullOrEmpty()) return + + for (updatedUser in users) { + if (updatedUser.handle == user?.handle) { + user = updatedUser + contentContactProperties.emailText.text = updatedUser.email + break } } } - override fun onUserAlertsUpdate(api: MegaApiJava, userAlerts: ArrayList) { + override fun onUserAlertsUpdate(api: MegaApiJava, userAlerts: ArrayList?) { Timber.d("onUserAlertsUpdate") } - override fun onNodesUpdate(api: MegaApiJava, nodeList: ArrayList) { + override fun onNodesUpdate(api: MegaApiJava, nodeList: ArrayList?) { sharedFoldersFragment?.let { if (it.isVisible) { it.setNodes(parentHandle) @@ -324,13 +324,13 @@ class ContactInfoActivity : BaseActivity(), ActionNodeCallback, MegaRequestListe override fun onAccountUpdate(api: MegaApiJava) {} override fun onContactRequestsUpdate( api: MegaApiJava, - requests: ArrayList, + requests: ArrayList?, ) { } - override fun onEvent(api: MegaApiJava, event: MegaEvent) {} - override fun onSetsUpdate(api: MegaApiJava, sets: ArrayList) {} - override fun onSetElementsUpdate(api: MegaApiJava, elements: ArrayList) {} + override fun onEvent(api: MegaApiJava, event: MegaEvent?) {} + override fun onSetsUpdate(api: MegaApiJava, sets: ArrayList?) {} + override fun onSetElementsUpdate(api: MegaApiJava, elements: ArrayList?) {} } private val megaChatRequestListenerInterface = object : MegaChatRequestListenerInterface { diff --git a/app/src/main/java/mega/privacy/android/app/main/FileExplorerActivity.kt b/app/src/main/java/mega/privacy/android/app/main/FileExplorerActivity.kt index 336424f528e..389d7e33495 100644 --- a/app/src/main/java/mega/privacy/android/app/main/FileExplorerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/FileExplorerActivity.kt @@ -2074,15 +2074,15 @@ class FileExplorerActivity : TransfersManagementActivity(), MegaRequestListenerI } - override fun onUsersUpdate(api: MegaApiJava, users: ArrayList) { + override fun onUsersUpdate(api: MegaApiJava, users: ArrayList?) { } - override fun onUserAlertsUpdate(api: MegaApiJava, userAlerts: ArrayList) { + override fun onUserAlertsUpdate(api: MegaApiJava, userAlerts: ArrayList?) { Timber.d("onUserAlertsUpdate") } - override fun onEvent(api: MegaApiJava, event: MegaEvent) {} + override fun onEvent(api: MegaApiJava, event: MegaEvent?) {} override fun onSetsUpdate(api: MegaApiJava?, sets: java.util.ArrayList?) { } @@ -2093,7 +2093,7 @@ class FileExplorerActivity : TransfersManagementActivity(), MegaRequestListenerI ) { } - override fun onNodesUpdate(api: MegaApiJava, updatedNodes: ArrayList) { + override fun onNodesUpdate(api: MegaApiJava, updatedNodes: ArrayList?) { Timber.d("onNodesUpdate") cDriveExplorer?.let { cDriveExplorer -> if (cloudExplorerFragment != null) { @@ -2170,7 +2170,7 @@ class FileExplorerActivity : TransfersManagementActivity(), MegaRequestListenerI override fun onContactRequestsUpdate( api: MegaApiJava, - requests: ArrayList, + requests: ArrayList?, ) { } diff --git a/app/src/main/java/mega/privacy/android/app/main/PdfViewerActivity.kt b/app/src/main/java/mega/privacy/android/app/main/PdfViewerActivity.kt index 5ed9c4bbb59..a33cd35521d 100644 --- a/app/src/main/java/mega/privacy/android/app/main/PdfViewerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/PdfViewerActivity.kt @@ -558,13 +558,13 @@ class PdfViewerActivity : BaseActivity(), MegaGlobalListenerInterface, OnPageCha nodeSaver.saveState(outState) } - override fun onUsersUpdate(api: MegaApiJava, users: ArrayList) {} + override fun onUsersUpdate(api: MegaApiJava, users: ArrayList?) {} - override fun onUserAlertsUpdate(api: MegaApiJava, userAlerts: ArrayList) { + override fun onUserAlertsUpdate(api: MegaApiJava, userAlerts: ArrayList?) { Timber.d("onUserAlertsUpdate") } - override fun onNodesUpdate(api: MegaApiJava, nodeList: ArrayList) { + override fun onNodesUpdate(api: MegaApiJava, nodeList: ArrayList?) { Timber.d("onNodesUpdate") if (megaApi.getNodeByHandle(handle) == null) { return @@ -578,15 +578,15 @@ class PdfViewerActivity : BaseActivity(), MegaGlobalListenerInterface, OnPageCha override fun onContactRequestsUpdate( api: MegaApiJava, - requests: ArrayList, + requests: ArrayList?, ) { } - override fun onEvent(api: MegaApiJava, event: MegaEvent) {} + override fun onEvent(api: MegaApiJava, event: MegaEvent?) {} - override fun onSetsUpdate(api: MegaApiJava, sets: ArrayList) {} + override fun onSetsUpdate(api: MegaApiJava, sets: ArrayList?) {} - override fun onSetElementsUpdate(api: MegaApiJava, elements: ArrayList) {} + override fun onSetElementsUpdate(api: MegaApiJava, elements: ArrayList?) {} override fun showSnackbar(type: Int, content: String?, chatId: Long) { showSnackbar(type, binding.pdfViewerContainer, content, chatId) diff --git a/app/src/main/java/mega/privacy/android/app/main/VersionsFileActivity.kt b/app/src/main/java/mega/privacy/android/app/main/VersionsFileActivity.kt index 653aa111a09..3ff600e7a82 100644 --- a/app/src/main/java/mega/privacy/android/app/main/VersionsFileActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/VersionsFileActivity.kt @@ -714,17 +714,17 @@ class VersionsFileActivity : PasscodeActivity(), MegaRequestListenerInterface, V adapter?.notifyDataSetChanged() } - override fun onUsersUpdate(api: MegaApiJava, users: ArrayList) { + override fun onUsersUpdate(api: MegaApiJava, users: ArrayList?) { Timber.d("onUsersUpdate") } - override fun onUserAlertsUpdate(api: MegaApiJava, userAlerts: ArrayList) { + override fun onUserAlertsUpdate(api: MegaApiJava, userAlerts: ArrayList?) { Timber.d("onUserAlertsUpdate") } - override fun onEvent(api: MegaApiJava, event: MegaEvent) {} - override fun onSetsUpdate(api: MegaApiJava, sets: ArrayList) {} - override fun onSetElementsUpdate(api: MegaApiJava, elements: ArrayList) {} + override fun onEvent(api: MegaApiJava, event: MegaEvent?) {} + override fun onSetsUpdate(api: MegaApiJava, sets: ArrayList?) {} + override fun onSetElementsUpdate(api: MegaApiJava, elements: ArrayList?) {} override fun onNodesUpdate(api: MegaApiJava, nodes: ArrayList?) { Timber.d("onNodesUpdate") var thisNode = false @@ -840,7 +840,7 @@ class VersionsFileActivity : PasscodeActivity(), MegaRequestListenerInterface, V override fun onContactRequestsUpdate( api: MegaApiJava, - requests: ArrayList, + requests: ArrayList?, ) { } From 4b407d6583dfb7a07338cedee99b91803bc9a688 Mon Sep 17 00:00:00 2001 From: Atiqur Rahman Date: Tue, 14 Mar 2023 11:53:20 +0600 Subject: [PATCH 233/334] CU-171: Fix video compression insufficient storage issue --- .../data/facade/VideoCompressionFacade.kt | 2 +- .../data/facade/VideoCompressionFacadeTest.kt | 20 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/data/src/main/java/mega/privacy/android/data/facade/VideoCompressionFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/VideoCompressionFacade.kt index 49978064386..ebebcd5b9bd 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/VideoCompressionFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/VideoCompressionFacade.kt @@ -76,7 +76,7 @@ internal class VideoCompressionFacade @Inject constructor(private val fileGatewa runCatching { attachment?.let { outputRoot?.run { - if (fileGateway.hasEnoughStorage( + if (!fileGateway.hasEnoughStorage( rootPath = this, File(it.originalPath) ) diff --git a/data/src/test/java/mega/privacy/android/data/facade/VideoCompressionFacadeTest.kt b/data/src/test/java/mega/privacy/android/data/facade/VideoCompressionFacadeTest.kt index 70e02bd596f..207491348df 100644 --- a/data/src/test/java/mega/privacy/android/data/facade/VideoCompressionFacadeTest.kt +++ b/data/src/test/java/mega/privacy/android/data/facade/VideoCompressionFacadeTest.kt @@ -10,7 +10,9 @@ import mega.privacy.android.domain.entity.VideoAttachment import mega.privacy.android.domain.entity.VideoCompressionState import org.junit.Before import org.junit.Test +import org.mockito.kotlin.any import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever import kotlin.contracts.ExperimentalContracts @OptIn(ExperimentalCoroutinesApi::class) @@ -43,7 +45,7 @@ class VideoCompressionFacadeTest { } @Test - fun `test that when video attachments are empty then compression ends`() = runTest { + fun `test that compression ends when video attachments are empty`() = runTest { underTest.start().test { val event = awaitItem() assertThat(event.javaClass).isEqualTo(VideoCompressionState.Finished::class.java) @@ -52,7 +54,7 @@ class VideoCompressionFacadeTest { } @Test - fun `test that when output roots is not set then insufficient storage event is emitted`() = + fun `test that insufficient storage event is emitted when output roots is not set`() = runTest { underTest.addItems(videoAttachments) underTest.start().test { @@ -62,4 +64,18 @@ class VideoCompressionFacadeTest { cancelAndConsumeRemainingEvents() } } + + @Test + fun `test that insufficient storage event is emitted when there is not enough storage`() = + runTest { + underTest.setOutputRoot("/path/to/root") + underTest.addItems(videoAttachments) + whenever(fileGateway.hasEnoughStorage(any(), any())).thenReturn(false) + underTest.start().test { + val event = awaitItem() + assertThat(event.javaClass) + .isEqualTo(VideoCompressionState.InsufficientStorage::class.java) + cancelAndConsumeRemainingEvents() + } + } } From b10a6393649249fa8391963cbdc9f101ea5fccbc Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Thu, 16 Mar 2023 16:48:05 +1300 Subject: [PATCH 234/334] T4681756: Hide NO_KEY folder name in the notification --- .../android/app/listeners/GlobalListener.kt | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/listeners/GlobalListener.kt b/app/src/main/java/mega/privacy/android/app/listeners/GlobalListener.kt index e9bacb66545..63c4ba457cc 100644 --- a/app/src/main/java/mega/privacy/android/app/listeners/GlobalListener.kt +++ b/app/src/main/java/mega/privacy/android/app/listeners/GlobalListener.kt @@ -261,18 +261,25 @@ class GlobalListener @Inject constructor( Timber.d("showSharedFolderNotification") try { val sharesIncoming = megaApi.inSharesList ?: return - var name: String? = "" - for (mS in sharesIncoming) { - if (mS.nodeHandle == n.handle) { - val user = megaApi.getContact(mS.user) - name = ContactUtil.getMegaUserNameDB(user) ?: "" - } - } + val notificationTitle = appContext.getString( + if (n.hasChanged(MegaNode.CHANGE_TYPE_NEW)) + R.string.title_incoming_folder_notification + else R.string.context_permissions_changed + ) + + val userName = sharesIncoming.firstOrNull { it.nodeHandle == n.handle }?.let { + val user = megaApi.getContact(it.user) + ContactUtil.getMegaUserNameDB(user) ?: "" + } ?: "" + + val folderName = if (n.isNodeKeyDecrypted) n.name else notificationTitle + val source = - "" + n.name + " " + appContext.getString(R.string.incoming_folder_notification) + " " + Util.toCDATA( - name - ) + "$folderName " + + appContext.getString(R.string.incoming_folder_notification) + + " " + + Util.toCDATA(userName) val notificationContent = HtmlCompat.fromHtml(source, HtmlCompat.FROM_HTML_MODE_LEGACY) val notificationChannelId = Constants.NOTIFICATION_CHANNEL_CLOUDDRIVE_ID val intent: Intent = Intent(appContext, ManagerActivity::class.java) @@ -282,8 +289,6 @@ class GlobalListener @Inject constructor( appContext, 0, intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE ) - val notificationTitle: String = - appContext.getString(if (n.hasChanged(MegaNode.CHANGE_TYPE_NEW)) R.string.title_incoming_folder_notification else R.string.context_permissions_changed) val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { From 40aa06f11e916b0155e00280c0f1458a20a161ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raquel=20Garc=C3=ADa=20Chico?= Date: Thu, 16 Mar 2023 14:21:25 +0100 Subject: [PATCH 235/334] Update megaSdkVersion 20230316.130004-rel --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 549a880e6c1..5861e6f59bd 100644 --- a/build.gradle +++ b/build.gradle @@ -69,7 +69,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230308.101554-rel' + megaSdkVersion = '20230316.130004-rel' // App dependencies accompanistLayoutVersion = '0.24.13-rc' From cf48a3b3f650f7ee6d0e3cc7adbb6c3278216e5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raquel=20Garc=C3=ADa=20Chico?= Date: Thu, 16 Mar 2023 15:07:57 +0100 Subject: [PATCH 236/334] Update SDK submodule --- sdk/src/main/jni/mega/sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index 5cb809761b4..c0cc0f193d8 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit 5cb809761b47fbc31723c1c4eaa70bb4d6bec5f8 +Subproject commit c0cc0f193d8bd9ccaeb8a02e8f667013484640c4 From 0c853faf46b4d2af755f91450234250c22903067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raquel=20Garc=C3=ADa=20Chico?= Date: Tue, 21 Mar 2023 11:54:56 +0100 Subject: [PATCH 237/334] Increase Version Name to v7.7.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5861e6f59bd..a4e3c274e82 100644 --- a/build.gradle +++ b/build.gradle @@ -60,7 +60,7 @@ task clean(type: Delete) { // Define versions in a single place ext { // App - appVersion = "7.7" + appVersion = "7.7.1" // Sdk and tools compileSdkVersion = 33 From 415a9c4653dc2dd4bfe84b0619d982a7cd2d7858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raquel=20Garc=C3=ADa=20Chico?= Date: Tue, 21 Mar 2023 11:57:35 +0100 Subject: [PATCH 238/334] Update SDK submodule --- sdk/src/main/jni/mega/sdk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index c0cc0f193d8..bd3c0251e49 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit c0cc0f193d8bd9ccaeb8a02e8f667013484640c4 +Subproject commit bd3c0251e49d9e08a84cca3f6e1452d41ffef267 From 1ab03bf65e718aa965871537c7e39818c4ced5f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raquel=20Garc=C3=ADa=20Chico?= Date: Tue, 21 Mar 2023 13:01:59 +0100 Subject: [PATCH 239/334] Update megaSdkVersion 20230321.115056-rel --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a4e3c274e82..a6e331c434f 100644 --- a/build.gradle +++ b/build.gradle @@ -69,7 +69,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230316.130004-rel' + megaSdkVersion = '20230321.115056-rel' // App dependencies accompanistLayoutVersion = '0.24.13-rc' From 983a022105d2aa818a6132813e993ae4734b0cbd Mon Sep 17 00:00:00 2001 From: Robin Shi Date: Thu, 23 Mar 2023 10:02:45 +1300 Subject: [PATCH 240/334] AND-16048 Gradle tasks cannot be executed twice with configuration cache (cherry picked from commit f0d165cb9bcece62a1de3369907fb4fc578d5d7f) --- gradle.properties | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index e4033505b5c..3d7450aa6bf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,6 +20,7 @@ android.jetifier.ignorelist=android-base-common,common # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true -org.gradle.unsafe.configuration-cache=true -# Use this flag carefully, in case some of the plugins are not fully compatible. -org.gradle.unsafe.configuration-cache-problems=warn \ No newline at end of file +# gradle configuration cache +#org.gradle.unsafe.configuration-cache=true +## Use this flag carefully, in case some of the plugins are not fully compatible. +#org.gradle.unsafe.configuration-cache-problems=warn \ No newline at end of file From c04c5c282b36cf6a82f2f241f0839a2fe4a4c706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yenel=20Rodr=C3=ADguez=20Hern=C3=A1ndez?= Date: Fri, 24 Mar 2023 07:08:06 +1300 Subject: [PATCH 241/334] Hotfix: T4724925 Remove onFetchNodesFinishedReceiver app/src/main/java/mega/privacy/android/app/globalmanagement/BackgroundRequestListener.kt domain/src/main/kotlin/mega/privacy/android/domain/usecase/login/LoginUseCase.kt --- .../BackgroundRequestListener.kt | 1 + .../app/presentation/login/LoginFragment.kt | 19 ------------------- .../app/presentation/login/LoginViewModel.kt | 11 +++++------ .../domain/usecase/login/DefaultLogin.kt | 5 +++++ .../usecase/login/DefaultLoginWith2FA.kt | 5 +++++ 5 files changed, 16 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/globalmanagement/BackgroundRequestListener.kt b/app/src/main/java/mega/privacy/android/app/globalmanagement/BackgroundRequestListener.kt index 9ed592173e9..8c3005bacae 100644 --- a/app/src/main/java/mega/privacy/android/app/globalmanagement/BackgroundRequestListener.kt +++ b/app/src/main/java/mega/privacy/android/app/globalmanagement/BackgroundRequestListener.kt @@ -150,6 +150,7 @@ class BackgroundRequestListener @Inject constructor( private fun handleFetchNodeRequest(e: MegaError) { Timber.d("TYPE_FETCH_NODES") + MegaApplication.isLoggingIn = false applicationScope.launch { broadcastFetchNodesFinish() } if (e.errorCode == MegaError.API_OK) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/login/LoginFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/login/LoginFragment.kt index e2b1fc02274..b468bdd6167 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/login/LoginFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/login/LoginFragment.kt @@ -930,24 +930,6 @@ class LoginFragment : Fragment() { viewModel.performLogin(typedEmail, typedPassword) } - /** - * Returns to login form page. - */ - private fun backToLoginForm() = with(binding) { - showLoginScreen() - - //reset 2fa page - login2fa.isVisible = false - progressbarVerify2fa.isVisible = false - pinFirstLogin.setText("") - pinSecondLogin.setText("") - pinThirdLogin.setText("") - pinFourthLogin.setText("") - pinFifthLogin.setText("") - pinSixthLogin.setText("") - loginEmailText.requestFocus() - } - private fun showLoginInProgress() = with(binding) { loginLoggingInText.isVisible = true loginFetchNodesText.isVisible = false @@ -1372,7 +1354,6 @@ class LoginFragment : Fragment() { .setCancelable(true) .setMessage(getString(R.string.confirm_cancel_login)) .setPositiveButton(getString(R.string.general_positive_button)) { _, _ -> - backToLoginForm() viewModel.stopLogin() }.setNegativeButton(getString(R.string.general_negative_button), null) .show() diff --git a/app/src/main/java/mega/privacy/android/app/presentation/login/LoginViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/login/LoginViewModel.kt index 24ee403a070..ba603f4f01e 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/login/LoginViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/login/LoginViewModel.kt @@ -151,7 +151,6 @@ class LoginViewModel @Inject constructor( with(state.value.pendingAction) { when (this) { ACTION_FORCE_RELOAD_ACCOUNT -> { - MegaApplication.isLoggingIn = false _state.update { it.copy(isPendingToFinishActivity = true) } } ACTION_OPEN_APP -> { @@ -208,7 +207,6 @@ class LoginViewModel @Inject constructor( * Stops logging in. */ fun stopLogin() { - MegaApplication.isLoggingIn = false _state.update { it.copy( pressedBackWhileLogin = true, @@ -227,6 +225,7 @@ class LoginViewModel @Inject constructor( ClearPsa { PsaManager::stopChecking } ) _state.update { it.copy(isLocalLogoutInProgress = false) } + MegaApplication.isLoggingIn = false } } @@ -373,8 +372,8 @@ class LoginViewModel @Inject constructor( DisableChatApi { MegaApplication.getInstance()::disableMegaChatApi } ).collectLatest { status -> status.checkStatus(email, password) } }.onFailure { exception -> - if (exception !is LoginException) return@onFailure MegaApplication.isLoggingIn = false + if (exception !is LoginException) return@onFailure if (exception is LoginMultiFactorAuthRequired) { _state.update { @@ -410,8 +409,8 @@ class LoginViewModel @Inject constructor( status.checkStatus() } }.onFailure { exception -> - if (exception !is LoginException) return@onFailure MegaApplication.isLoggingIn = false + if (exception !is LoginException) return@onFailure if (exception is LoginWrongMultiFactorAuth) { _state.update { @@ -440,8 +439,8 @@ class LoginViewModel @Inject constructor( DisableChatApi { MegaApplication.getInstance()::disableMegaChatApi } ).collectLatest { status -> status.checkStatus() } }.onFailure { exception -> - if (exception !is LoginException) return@onFailure MegaApplication.isLoggingIn = false + if (exception !is LoginException) return@onFailure exception.loginFailed() } } @@ -527,9 +526,9 @@ class LoginViewModel @Inject constructor( } } }.onFailure { + MegaApplication.isLoggingIn = false if (it !is FetchNodesException) return@launch - MegaApplication.isLoggingIn = false _state.update { state -> state.copy( isLoginInProgress = false, diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/login/DefaultLogin.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/login/DefaultLogin.kt index ed0523b2b93..8153ac8094a 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/login/DefaultLogin.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/login/DefaultLogin.kt @@ -1,5 +1,6 @@ package mega.privacy.android.domain.usecase.login +import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.sync.Mutex @@ -64,5 +65,9 @@ class DefaultLogin @Inject constructor( loginMutex.unlock() throw it } + + awaitClose { + loginMutex.unlock() + } } } \ No newline at end of file diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/login/DefaultLoginWith2FA.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/login/DefaultLoginWith2FA.kt index e4a14392849..3cfca828469 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/login/DefaultLoginWith2FA.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/login/DefaultLoginWith2FA.kt @@ -1,5 +1,6 @@ package mega.privacy.android.domain.usecase.login +import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.sync.Mutex @@ -52,5 +53,9 @@ class DefaultLoginWith2FA @Inject constructor( loginMutex.unlock() throw it } + + awaitClose { + loginMutex.unlock() + } } } \ No newline at end of file From 001a7d918505eeb8e98e06bbba243617fe96cdc1 Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Fri, 24 Mar 2023 18:07:18 +1300 Subject: [PATCH 242/334] CU-140: Remove upload limit of 1000 files - FIX --- .../app/cameraupload/CameraUploadsService.kt | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/cameraupload/CameraUploadsService.kt b/app/src/main/java/mega/privacy/android/app/cameraupload/CameraUploadsService.kt index 98fe7ef6096..36930eefd53 100644 --- a/app/src/main/java/mega/privacy/android/app/cameraupload/CameraUploadsService.kt +++ b/app/src/main/java/mega/privacy/android/app/cameraupload/CameraUploadsService.kt @@ -672,9 +672,12 @@ class CameraUploadsService : LifecycleService() { */ override fun onDestroy() { Timber.d("Service destroys.") - super.onDestroy() stopActiveHeartbeat() + stopWakeAndWifiLocks() coroutineScope?.cancel() + stopForeground(STOP_FOREGROUND_REMOVE) + cancelNotification() + super.onDestroy() } /** @@ -704,6 +707,8 @@ class CameraUploadsService : LifecycleService() { aborted = aborted ) } + } else { + finishService() } } else -> { @@ -1589,7 +1594,6 @@ class CameraUploadsService : LifecycleService() { aborted: Boolean = false, ) { Timber.d("Finish Camera upload process.") - stopWakeAndWifiLocks() if (coroutineScope?.isActive == true) { sendStatusToBackupCenter(aborted = aborted) @@ -1599,8 +1603,13 @@ class CameraUploadsService : LifecycleService() { coroutineScope?.cancel(CancellationException(cancelMessage)) } - stopForeground(STOP_FOREGROUND_REMOVE) - cancelNotification() + finishService() + } + + /** + * Stop the service + */ + private fun finishService() { stopSelf() } From 24faa2cde2ebb6076ed43ab7122f63bf29a82d12 Mon Sep 17 00:00:00 2001 From: Luong Hai Date: Fri, 24 Mar 2023 17:38:09 +1300 Subject: [PATCH 243/334] AND-16059: Fix Legacy ANR issue when download empty file (cherry picked from commit 1553e4ff9d3f59bdc224168cc1c17854e3e5a75c) --- .../android/app/main/adapters/MegaTransfersAdapter.kt | 8 +++++--- .../android/app/main/managerSections/TransfersFragment.kt | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaTransfersAdapter.kt b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaTransfersAdapter.kt index 4f347ecde3e..d6342a3605a 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaTransfersAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaTransfersAdapter.kt @@ -490,9 +490,11 @@ class MegaTransfersAdapter( * @return The progress of the transfer. */ private fun getProgress(transfer: MegaTransfer) = - context.getString(R.string.progress_size_indicator, - (100.0 * transfer.transferredBytes / transfer.totalBytes).roundToLong(), - Util.getSizeString(transfer.totalBytes)) + context.getString( + R.string.progress_size_indicator, + if (transfer.totalBytes > 0L) (100.0 * transfer.transferredBytes / transfer.totalBytes).roundToLong() else 0L, + Util.getSizeString(transfer.totalBytes) + ) /** * Selects or deselects a transfer with an animation view. diff --git a/app/src/main/java/mega/privacy/android/app/main/managerSections/TransfersFragment.kt b/app/src/main/java/mega/privacy/android/app/main/managerSections/TransfersFragment.kt index 2bc9c1c0fc5..22e0cd2a101 100644 --- a/app/src/main/java/mega/privacy/android/app/main/managerSections/TransfersFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/main/managerSections/TransfersFragment.kt @@ -7,6 +7,7 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode +import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope @@ -281,7 +282,7 @@ class TransfersFragment : TransfersBaseFragment(), SelectModeInterface, adapter?.addItemData(transfers, transfers.indexOf(transfersState.updatedTransfer)) - if (transfers.size == 1) { + if (transfers.isNotEmpty() && binding.transfersEmptyImage.isVisible) { setEmptyView(transfers.size) } } From 2333634729b5cc507ed434ccdccb5c002119f8e2 Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Mon, 27 Mar 2023 09:48:52 +0700 Subject: [PATCH 244/334] AND-15918: Fix Avatar Crash when process re-create (cherry picked from commit 6cec524455a6ea7df22f667e8cd6920820fb6e1b) --- .../app/myAccount/MyAccountViewModel.kt | 5 +-- .../presentation/avatar/view/AvatarView.kt | 41 ++++++++++--------- .../presentation/manager/UserInfoViewModel.kt | 27 ++++++------ .../repository/DefaultAccountRepository.kt | 6 ++- 4 files changed, 41 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/myAccount/MyAccountViewModel.kt b/app/src/main/java/mega/privacy/android/app/myAccount/MyAccountViewModel.kt index 0c2b6a4c88f..9c28534f5fb 100644 --- a/app/src/main/java/mega/privacy/android/app/myAccount/MyAccountViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/myAccount/MyAccountViewModel.kt @@ -916,15 +916,14 @@ class MyAccountViewModel @Inject constructor( */ private fun addProfileAvatar(path: String?) { val app = MegaApplication.getInstance() - val myEmail = megaApi.myUser.email + val myEmail = megaApi.myUser?.email val imgFile = if (!path.isNullOrEmpty()) File(path) else CacheFolderManager.getCacheFile( app, CacheFolderManager.TEMPORARY_FOLDER, "picture.jpg" ) - - if (!FileUtil.isFileAvailable(imgFile)) { + if (!FileUtil.isFileAvailable(imgFile) || myEmail.isNullOrEmpty()) { showResult(context.getString(R.string.general_error)) return } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/avatar/view/AvatarView.kt b/app/src/main/java/mega/privacy/android/app/presentation/avatar/view/AvatarView.kt index d9976cd53c6..a8d086695a3 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/avatar/view/AvatarView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/avatar/view/AvatarView.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.key import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -68,8 +69,7 @@ fun Avatar( is PhotoAvatarContent -> { PhotoAvatar( modifier = modifier, - photoPath = content.path, - showBorder = content.showBorder + content = content, ) } } @@ -143,25 +143,26 @@ fun EmojiAvatar( @Composable fun PhotoAvatar( modifier: Modifier = Modifier, - photoPath: String, - showBorder: Boolean = true, + content: PhotoAvatarContent, ) { - AsyncImage( - modifier = modifier - .clip(CircleShape) - .testTag("PhotoAvatar") - .run { - if (showBorder) { - border( - width = 3.dp, - color = borderColor(), - shape = CircleShape - ) - } else this - }, - model = photoPath, - contentDescription = "Photo Avatar" - ) + key(content.size, content.path) { + AsyncImage( + modifier = modifier + .clip(CircleShape) + .testTag("PhotoAvatar") + .run { + if (content.showBorder) { + border( + width = 3.dp, + color = borderColor(), + shape = CircleShape + ) + } else this + }, + model = content.path, + contentDescription = "Photo Avatar" + ) + } } @Composable diff --git a/app/src/main/java/mega/privacy/android/app/presentation/manager/UserInfoViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/manager/UserInfoViewModel.kt index 7de1b806020..639bcf57eed 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/manager/UserInfoViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/manager/UserInfoViewModel.kt @@ -86,6 +86,7 @@ internal class UserInfoViewModel @Inject constructor( monitorMyAvatarFile() .catch { Timber.e(it) } .collect { + Timber.d("CongHai - UserInfoViewModel monitorMyAvatarFile trigger") getUserAvatarOrDefault(isForceRefresh = false) } } @@ -98,21 +99,19 @@ internal class UserInfoViewModel @Inject constructor( } private suspend fun getUserAvatarOrDefault(isForceRefresh: Boolean) { - viewModelScope.launch { - val avatarFile = runCatching { getMyAvatarFile(isForceRefresh) } - .onFailure { Timber.e(it) }.getOrNull() - val avatarContent = avatarContentMapper( - fullName = _state.value.fullName, - localFile = avatarFile, - backgroundColor = { getMyAvatarColor() }, - showBorder = false, - textSize = 36.sp + val avatarFile = runCatching { getMyAvatarFile(isForceRefresh) } + .onFailure { Timber.e(it) }.getOrNull() + val avatarContent = avatarContentMapper( + fullName = _state.value.fullName, + localFile = avatarFile, + backgroundColor = { getMyAvatarColor() }, + showBorder = false, + textSize = 36.sp + ) + _state.update { + it.copy( + avatarContent = avatarContent, ) - _state.update { - it.copy( - avatarContent = avatarContent, - ) - } } } diff --git a/data/src/main/java/mega/privacy/android/data/repository/DefaultAccountRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/DefaultAccountRepository.kt index d60a9762b33..39e3d8aa212 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/DefaultAccountRepository.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/DefaultAccountRepository.kt @@ -476,7 +476,11 @@ internal class DefaultAccountRepository @Inject constructor( override suspend fun getAccountEmail(forceRefresh: Boolean): String? { if (forceRefresh) { return megaApiGateway.accountEmail - .also { dbHandler.saveMyEmail(it) } + .also { + if (it.isNullOrBlank().not()) { + dbHandler.saveMyEmail(it) + } + } } return dbHandler.myEmail } From e96b13c3827ba1166e24f9014b0c01d9378c0420 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Mon, 27 Mar 2023 18:51:17 +1300 Subject: [PATCH 245/334] AND-15960 Fix Bug Backup Recovery Key Stuck on Loading --- .../app/presentation/testpassword/TestPasswordActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/testpassword/TestPasswordActivity.kt b/app/src/main/java/mega/privacy/android/app/presentation/testpassword/TestPasswordActivity.kt index be0759f9fa1..52e73e1ff69 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/testpassword/TestPasswordActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/testpassword/TestPasswordActivity.kt @@ -121,7 +121,8 @@ class TestPasswordActivity : PasscodeActivity(), MegaRequestListenerInterface { numRequests-- if (uiState.isPasswordReminderNotified == PasswordState.True) { - if (dismissPasswordReminder && isLogout && numRequests <= 0) { + // numRequests is temporarily changed to 1 because copy, print, export Recovery Key hasn't been implemented yet in this activity. This is a quick fix for 7.8 + if (dismissPasswordReminder && isLogout && numRequests <= 1) { logout(this, megaApi, sharingScope) } } From 257a59420629c4f3b4f86c08b23bcc56ad2a6137 Mon Sep 17 00:00:00 2001 From: Gregg Meyrick Jover Date: Mon, 27 Mar 2023 22:18:51 +1300 Subject: [PATCH 246/334] AND-15898: Fix List Grid View Handling in Shares Section AND-15898: Fix List Grid View Handling in Shares Section Changelog: - Rename isList to currentViewType in ViewModels. - Move currentViewType to each ViewModel UI State. - Replace all references of ManagerActivity.isList with currentViewType in IncomingSharesFragment and OutgoingSharesFragment. - Reorganize the setting of UI Elements in MegaNodeBaseFragment and the Shares Fragments. - Reorganize the updating of UI Elements in IncomingSharesFragment and OutgoingSharesFragment, when receiving a View Type update. - Change how sharesPageAdapter is being instantiated in ManagerActivity. - Remove the launching of Coroutines when observing showDialogEvent. - Remove Build Warnings in several classes. - Add Tests for each Shares ViewModel. --- .../homepage/SortByHeaderViewModel.kt | 1 - .../android/app/main/ManagerActivity.kt | 4 +- .../app/main/adapters/MegaNodeAdapter.java | 12 +- .../clouddrive/FileBrowserFragment.kt | 2 +- .../app/presentation/search/SearchFragment.kt | 3 +- .../shares/MegaNodeBaseFragment.kt | 282 +++++++++--------- .../shares/incoming/IncomingSharesFragment.kt | 186 +++++++----- .../incoming/IncomingSharesViewModel.kt | 11 +- .../incoming/model/IncomingSharesState.kt | 19 +- .../shares/links/LinksFragment.kt | 66 ++-- .../shares/outgoing/OutgoingSharesFragment.kt | 173 ++++++----- .../outgoing/OutgoingSharesViewModel.kt | 19 +- .../outgoing/model/OutgoingSharesState.kt | 18 +- .../incoming/IncomingSharesViewModelTest.kt | 18 ++ .../outgoing/OutgoingSharesViewModelTest.kt | 18 ++ 15 files changed, 484 insertions(+), 348 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/homepage/SortByHeaderViewModel.kt b/app/src/main/java/mega/privacy/android/app/fragments/homepage/SortByHeaderViewModel.kt index 8a6f2e0c8c6..b8413ec3773 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/homepage/SortByHeaderViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/homepage/SortByHeaderViewModel.kt @@ -155,7 +155,6 @@ class SortByHeaderViewModel @Inject constructor( } } - /** * Previously Selected Order */ diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt index ea72400c796..1e9f4614ab9 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt @@ -3902,7 +3902,9 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf Timber.e(e, "Exception NotificationManager - remove contact notification") } - viewPagerShares.adapter = sharesPageAdapter + if (viewPagerShares.adapter == null) { + viewPagerShares.adapter = sharesPageAdapter + } TabLayoutMediator( tabLayoutShares, viewPagerShares diff --git a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java index d8eb5e44c27..edb6bd6a2ef 100644 --- a/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/main/adapters/MegaNodeAdapter.java @@ -656,14 +656,14 @@ public void setNodes(List nodes) { * Set the nodes list and shareData list * This function is used to populate the list of incoming and outgoing shares * - * @param nodes the list of nodes, whether verified or unverified + * @param nodes the list of nodes, whether verified or unverified * @param shareData the list of shares data associated to the node */ public void setNodesWithShareData(List nodes, List shareData) { this.nodes = insertPlaceHolderNode(nodes); // need to add extra elements to sharedata too, so that the element at a specific position // corresponds exactly to the node in the nodes list - for (int i = 0; i < this.nodes.size() - shareData.size(); i ++) { + for (int i = 0; i < this.nodes.size() - shareData.size(); i++) { shareData.add(0, null); } this.shareData = shareData; @@ -1007,8 +1007,8 @@ public void onBindViewHolderList(ViewHolderBrowserList holder, int position) { holder.textViewFileSize.setText(""); holder.imageFavourite.setVisibility(type != INCOMING_SHARES_ADAPTER - && type != FOLDER_LINK_ADAPTER - && node.isFavourite() ? View.VISIBLE : View.GONE); + && type != FOLDER_LINK_ADAPTER + && node.isFavourite() ? View.VISIBLE : View.GONE); if (type != FOLDER_LINK_ADAPTER && node.getLabel() != MegaNode.NODE_LBL_UNKNOWN) { Drawable drawable = MegaNodeUtil.getNodeLabelDrawable(node.getLabel(), holder.itemView.getResources()); @@ -1301,7 +1301,7 @@ public void onClick(View v) { } } - public void filClicked(int currentPosition) { + public void reselectUnHandledSingleItem(int currentPosition) { notifyItemChanged(currentPosition); unHandledItem = currentPosition; } @@ -1560,7 +1560,7 @@ public void onCancelClicked() { */ private void showUnverifiedNodeUi(ViewHolderBrowserList holder, Boolean isIncomingNode, MegaNode node, ShareData shareData) { if (isIncomingNode) { - if(node.isNodeKeyDecrypted()) { + if (node.isNodeKeyDecrypted()) { holder.textViewFileName.setText(node.getName()); } else { holder.textViewFileName.setText(context.getString(R.string.shared_items_verify_credentials_undecrypted_folder)); diff --git a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt index a5b577f2597..a018d0b078a 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt @@ -332,7 +332,7 @@ class FileBrowserFragment : RotatableFragment() { * reselectUnHandledSingleItem */ override fun reselectUnHandledSingleItem(position: Int) { - megaNodeAdapter?.filClicked(position) + megaNodeAdapter?.reselectUnHandledSingleItem(position) } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt index a4c163acb03..1339966db78 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt @@ -658,7 +658,8 @@ class SearchFragment : RotatableFragment() { override fun multipleItemClick(position: Int) = adapter.toggleSelection(position) - override fun reselectUnHandledSingleItem(position: Int) = adapter.filClicked(position) + override fun reselectUnHandledSingleItem(position: Int) = + adapter.reselectUnHandledSingleItem(position) override fun updateActionModeTitle() { actionMode?.let { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/MegaNodeBaseFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/MegaNodeBaseFragment.kt index 5f845628782..7d42bfd9b24 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/MegaNodeBaseFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/MegaNodeBaseFragment.kt @@ -11,25 +11,21 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.widget.ImageView -import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast import androidx.appcompat.view.ActionMode import androidx.core.content.FileProvider import androidx.core.text.HtmlCompat import androidx.fragment.app.viewModels -import androidx.recyclerview.widget.DefaultItemAnimator -import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint import mega.privacy.android.app.MimeTypeList import mega.privacy.android.app.R -import mega.privacy.android.app.components.CustomizedGridLayoutManager -import mega.privacy.android.app.components.PositionDividerItemDecoration +import mega.privacy.android.app.components.NewGridRecyclerView import mega.privacy.android.app.components.dragger.DragToExitSupport.Companion.observeDragSupportEvents import mega.privacy.android.app.components.dragger.DragToExitSupport.Companion.putThumbnailLocation import mega.privacy.android.app.components.scrollBar.FastScroller -import mega.privacy.android.app.fragments.homepage.EventObserver +import mega.privacy.android.app.databinding.FragmentFileBrowserBinding import mega.privacy.android.app.fragments.homepage.SortByHeaderViewModel import mega.privacy.android.app.imageviewer.ImageViewerActivity.Companion.getIntentForParentNode import mega.privacy.android.app.interfaces.SnackbarShower @@ -50,8 +46,8 @@ import mega.privacy.android.app.utils.MegaNodeUtil.manageURLNode import mega.privacy.android.app.utils.MegaNodeUtil.onNodeTapped import mega.privacy.android.app.utils.MegaNodeUtil.shareNodes import mega.privacy.android.app.utils.MegaNodeUtil.showConfirmationLeaveIncomingShares -import mega.privacy.android.app.utils.StringResourcesUtils import mega.privacy.android.app.utils.Util +import mega.privacy.android.app.utils.displayMetrics import mega.privacy.android.data.qualifier.MegaApi import mega.privacy.android.domain.entity.SortOrder import nz.mega.sdk.MegaApiAndroid @@ -79,7 +75,7 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { * Number of items in the adapter */ val itemCount: Int - get() = adapter?.itemCount ?: 0 + get() = megaNodeAdapter?.itemCount ?: 0 /** * viewModel responsible for sorting the list @@ -89,7 +85,7 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { /** * Adapter holding the list of nodes */ - protected var adapter: MegaNodeAdapter? = null + protected var megaNodeAdapter: MegaNodeAdapter? = null /** * Activity bound to the fragment @@ -102,13 +98,22 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { protected var actionMode: ActionMode? = null /** UI Components*/ - protected var fastScroller: FastScroller? = null - protected var recyclerView: RecyclerView? = null - protected var mLayoutManager: LinearLayoutManager? = null - protected var gridLayoutManager: CustomizedGridLayoutManager? = null - protected var emptyImageView: ImageView? = null - protected var emptyLinearLayout: LinearLayout? = null - protected var emptyTextViewFirst: TextView? = null + private var fastScroller: FastScroller? = null + + /** + * The [NewGridRecyclerView] to display the list of items from [megaNodeAdapter] + */ + protected var recyclerView: NewGridRecyclerView? = null + + /** + * The [ImageView] that is displayed when there are no items in [megaNodeAdapter] + */ + protected var emptyListImageView: ImageView? = null + + /** + * The [TextView] that is displayed when there are no items in [megaNodeAdapter] + */ + private var emptyListTextView: TextView? = null /** * Viewer identifier corresponding to the fragment @@ -152,13 +157,18 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { */ protected abstract fun showSortByPanel() + /** + * onViewCreated + */ override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) recyclerView?.let { observeDragSupportEvents(viewLifecycleOwner, it, viewerFrom) } checkScroll() - setupObservers() } + /** + * onAttach + */ override fun onAttach(context: Context) { super.onAttach(context) if (context is ManagerActivity) { @@ -166,32 +176,50 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { } } + /** + * onDestroy + */ override fun onDestroy() { - adapter?.clearTakenDownDialog() + megaNodeAdapter?.clearTakenDownDialog() super.onDestroy() } - override fun getAdapter(): RotatableAdapter? = adapter + /** + * getAdapter + */ + override fun getAdapter(): RotatableAdapter? = megaNodeAdapter + /** + * activateActionMode + */ override fun activateActionMode() { - if (adapter?.isMultipleSelect == false) - adapter?.isMultipleSelect = true + if (megaNodeAdapter?.isMultipleSelect == false) + megaNodeAdapter?.isMultipleSelect = true } + /** + * multipleItemClick + */ override fun multipleItemClick(position: Int) { - adapter?.toggleSelection(position) + megaNodeAdapter?.toggleSelection(position) } + /** + * reselectUnHandledSingleItem + */ override fun reselectUnHandledSingleItem(position: Int) { - adapter?.filClicked(position) + megaNodeAdapter?.reselectUnHandledSingleItem(position) } + /** + * updateActionModeTitle + */ override fun updateActionModeTitle() { - if (actionMode == null || activity == null || adapter == null) + if (actionMode == null || activity == null || megaNodeAdapter == null) return - val files = adapter?.selectedNodes?.filter { it.isFile }?.size ?: 0 - val folders = adapter?.selectedNodes?.filter { it.isFolder }?.size ?: 0 + val files = megaNodeAdapter?.selectedNodes?.filter { it.isFile }?.size ?: 0 + val folders = megaNodeAdapter?.selectedNodes?.filter { it.isFolder }?.size ?: 0 actionMode?.title = when { (files == 0 && folders == 0) -> 0.toString() @@ -208,20 +236,66 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { } } + /** + * Establishes the UI + */ + protected fun setupUI(inflater: LayoutInflater, container: ViewGroup?): View { + val binding = FragmentFileBrowserBinding.inflate(inflater, container, false) + + recyclerView = binding.fileBrowserRecyclerView + fastScroller = binding.fileBrowserFastScroller + emptyListImageView = binding.fileBrowserEmptyListImage + emptyListTextView = binding.fileBrowserEmptyListText + + setupFastScroller() + setupRecyclerView() + + return binding.root + } + + /** + * Establishes the [FastScroller] for the [NewGridRecyclerView] + */ + private fun setupFastScroller() = fastScroller?.setRecyclerView(recyclerView) + + /** + * Establishes the [NewGridRecyclerView] + */ + private fun setupRecyclerView() { + recyclerView?.apply { + setPadding( + 0, + 0, + 0, + Util.dp2px(85.toFloat(), displayMetrics()) + ) + clipToPadding = false + setHasFixedSize(true) + addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + if (managerActivity?.tabItemShares == currentSharesTab) { + checkScroll() + } + } + }) + } + } + /** * Method to update an item when a nickname is added, updated or removed from a contact. * * @param contactHandle Contact ID. */ fun updateContact(contactHandle: Long) { - adapter?.updateItem(contactHandle) + megaNodeAdapter?.updateItem(contactHandle) } /** * Select all items */ fun selectAll() { - adapter?.let { + megaNodeAdapter?.let { activateActionMode() it.selectAll() updateActionModeTitle() @@ -240,8 +314,8 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { * Deactivate action mode */ fun hideMultipleSelect() { - if (adapter?.isMultipleSelect == true) { - adapter?.isMultipleSelect = false + if (megaNodeAdapter?.isMultipleSelect == true) { + megaNodeAdapter?.isMultipleSelect = false actionMode?.finish() } } @@ -250,15 +324,8 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { * Clear the selected nodes */ fun clearSelections() { - if (adapter?.isMultipleSelect == true) - adapter?.clearSelections() - } - - /** - * Refresh the list - */ - fun notifyDataSetChanged() { - adapter?.notifyDataSetChanged() + if (megaNodeAdapter?.isMultipleSelect == true) + megaNodeAdapter?.clearSelections() } /** @@ -266,43 +333,34 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { */ fun visibilityFastScroller() { fastScroller?.visibility = - if (adapter == null || (adapter ?: return).itemCount < Constants.MIN_ITEMS_SCROLLBAR) + if (megaNodeAdapter == null || (megaNodeAdapter + ?: return).itemCount < Constants.MIN_ITEMS_SCROLLBAR + ) View.GONE else View.VISIBLE } - /** - * Setup ViewModel observers - */ - private fun setupObservers() { - sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, EventObserver { - showSortByPanel() - }) - } - /** * Display the elevation of the app bar or not */ fun checkScroll() { val withElevation = (recyclerView?.canScrollVertically(-1) == true && recyclerView?.visibility == View.VISIBLE) - || adapter?.isMultipleSelect == true + || megaNodeAdapter?.isMultipleSelect == true managerActivity?.changeAppBarElevation(withElevation) } /** * Display the empty view or not */ - protected fun checkEmptyView() { - if (adapter?.itemCount == 0) { + private fun checkEmptyView() { + if (megaNodeAdapter?.itemCount == 0) { recyclerView?.visibility = View.GONE - emptyImageView?.visibility = View.VISIBLE - emptyLinearLayout?.visibility = View.VISIBLE + emptyListImageView?.visibility = View.VISIBLE } else { recyclerView?.visibility = View.VISIBLE - emptyImageView?.visibility = View.GONE - emptyLinearLayout?.visibility = View.GONE + emptyListImageView?.visibility = View.GONE } } @@ -344,7 +402,7 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { intent.apply { putExtra("position", position) - putExtra("placeholder", adapter?.placeholderCount ?: 0) + putExtra("placeholder", megaNodeAdapter?.placeholderCount ?: 0) putExtra("parentNodeHandle", parentHandle) putExtra("orderGetChildren", sortOrder) putExtra("adapterType", fragmentAdapter) @@ -503,99 +561,32 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { recyclerView ?: return, position, viewerFrom, - adapter ?: return + megaNodeAdapter ?: return ) startActivity(intent) managerActivity?.overridePendingTransition(0, 0) } else { Toast.makeText( requireContext(), - StringResourcesUtils.getString(R.string.intent_not_available), + getString(R.string.intent_not_available), Toast.LENGTH_LONG ).show() } } } - /** - * Inflate a vertical list - */ - protected fun getListView(inflater: LayoutInflater, container: ViewGroup?): View { - val v = inflater.inflate(R.layout.fragment_filebrowserlist, container, false) - recyclerView = v.findViewById(R.id.file_list_view_browser) - mLayoutManager = LinearLayoutManager(requireContext()) - recyclerView?.layoutManager = mLayoutManager - recyclerView?.addItemDecoration( - PositionDividerItemDecoration( - requireContext(), - resources.displayMetrics - ) - ) - fastScroller = v.findViewById(R.id.fastscroll) - setRecyclerView() - recyclerView?.itemAnimator = Util.noChangeRecyclerViewItemAnimator() - emptyImageView = v.findViewById(R.id.file_list_empty_image) - emptyLinearLayout = v.findViewById(R.id.file_list_empty_text) - emptyTextViewFirst = v.findViewById(R.id.file_list_empty_text_first) - adapter?.adapterType = MegaNodeAdapter.ITEM_VIEW_TYPE_LIST - - return v - } - - /** - * Inflate a grid list - */ - protected fun getGridView(inflater: LayoutInflater, container: ViewGroup?): View { - val v = inflater.inflate(R.layout.fragment_filebrowsergrid, container, false) - recyclerView = v.findViewById(R.id.file_grid_view_browser) - gridLayoutManager = recyclerView?.layoutManager as CustomizedGridLayoutManager? - fastScroller = v.findViewById(R.id.fastscroll) - setRecyclerView() - recyclerView?.itemAnimator = DefaultItemAnimator() - emptyImageView = v.findViewById(R.id.file_grid_empty_image) - emptyLinearLayout = v.findViewById(R.id.file_grid_empty_text) - emptyTextViewFirst = v.findViewById(R.id.file_grid_empty_text_first) - adapter?.adapterType = MegaNodeAdapter.ITEM_VIEW_TYPE_GRID - - return v - } - - /** - * Setup the recyclerview - */ - private fun setRecyclerView() { - recyclerView?.setPadding( - 0, - 0, - 0, - Util.dp2px(85.toFloat(), resources.displayMetrics) - ) - recyclerView?.setHasFixedSize(true) - recyclerView?.clipToPadding = false - recyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - super.onScrolled(recyclerView, dx, dy) - if (managerActivity?.tabItemShares == currentSharesTab) { - checkScroll() - } - } - }) - fastScroller?.setRecyclerView(recyclerView) - } - /** * Setup the empty view */ protected fun setFinalEmptyView(initialText: String?) { - var text = initialText ?: run { - emptyImageView?.setImageResource( + emptyListImageView?.setImageResource( if (Util.isScreenInPortrait(requireContext())) R.drawable.empty_folder_portrait else R.drawable.empty_folder_landscape ) - StringResourcesUtils.getString(R.string.file_browser_empty_folder_new) + getString(R.string.file_browser_empty_folder_new) } try { @@ -614,7 +605,7 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { } catch (e: Exception) { Timber.w(e, "Exception formatting text") } - emptyTextViewFirst?.text = HtmlCompat.fromHtml(text, HtmlCompat.FROM_HTML_MODE_LEGACY) + emptyListTextView?.text = HtmlCompat.fromHtml(text, HtmlCompat.FROM_HTML_MODE_LEGACY) checkEmptyView() } @@ -628,6 +619,9 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { */ protected var selected: List = ArrayList() + /** + * onCreateActionMode + */ override fun onCreateActionMode(actionMode: ActionMode, menu: Menu): Boolean { val inflater = actionMode.menuInflater inflater.inflate(R.menu.cloud_storage_action, menu) @@ -640,13 +634,19 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { return true } + /** + * onPrepareActionMode + */ override fun onPrepareActionMode(actionMode: ActionMode, menu: Menu): Boolean { - selected = adapter?.selectedNodes ?: return false + selected = megaNodeAdapter?.selectedNodes ?: return false menu.findItem(R.id.cab_menu_share_link).title = - StringResourcesUtils.getQuantityString(R.plurals.get_links, selected.size) + resources.getQuantityString(R.plurals.get_links, selected.size) return false } + /** + * onActionItemClicked + */ override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean { Timber.d("onActionItemClicked") @@ -655,7 +655,13 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { val nC = NodeController(requireActivity()) when (item.itemId) { R.id.cab_menu_download -> { - managerActivity?.saveNodesToDevice(selected, false, false, false, false) + managerActivity?.saveNodesToDevice( + nodes = selected, + highPriority = false, + isFolderLink = false, + fromMediaViewer = false, + fromChat = false, + ) hideActionMode() } R.id.cab_menu_rename -> { @@ -692,7 +698,7 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { (requireActivity() as SnackbarShower), handleList ) R.id.cab_menu_send_to_chat -> { - adapter?.arrayListSelectedNodes?.let { + megaNodeAdapter?.arrayListSelectedNodes?.let { managerActivity?.attachNodesToChats(it) } hideActionMode() @@ -707,9 +713,12 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { return true } + /** + * onDestroyActionMode + */ override fun onDestroyActionMode(actionMode: ActionMode) { clearSelections() - adapter?.isMultipleSelect = false + megaNodeAdapter?.isMultipleSelect = false if (requireActivity() is ManagerActivity) { managerActivity?.showFabButton() managerActivity?.hideTabs(false, currentTab) @@ -724,8 +733,9 @@ abstract class MegaNodeBaseFragment : RotatableFragment() { * @return true if all nodes selected */ protected fun notAllNodesSelected(): Boolean { - return selected.size < (adapter?.itemCount?.minus((adapter?.placeholderCount) ?: 0) - ?: 0) + return selected.size < (megaNodeAdapter?.itemCount?.minus( + (megaNodeAdapter?.placeholderCount) ?: 0 + ) ?: 0) } } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index ee77680cac3..88dfedfa7db 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -13,11 +13,15 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.DefaultItemAnimator import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import mega.privacy.android.app.R import mega.privacy.android.app.arch.extensions.collectFlow -import mega.privacy.android.app.components.NewGridRecyclerView +import mega.privacy.android.app.components.CustomizedGridLayoutManager +import mega.privacy.android.app.components.PositionDividerItemDecoration +import mega.privacy.android.app.fragments.homepage.EventObserver +import mega.privacy.android.app.fragments.homepage.SortByHeaderViewModel import mega.privacy.android.app.main.adapters.MegaNodeAdapter import mega.privacy.android.app.main.controllers.NodeController import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity @@ -35,6 +39,7 @@ import mega.privacy.android.app.utils.MegaNodeUtil.allHaveFullAccess import mega.privacy.android.app.utils.MegaNodeUtil.areAllFileNodesAndNotTakenDown import mega.privacy.android.app.utils.MegaNodeUtil.areAllNotTakenDown import mega.privacy.android.app.utils.Util +import mega.privacy.android.app.utils.displayMetrics import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.preference.ViewType @@ -49,13 +54,16 @@ import java.util.Collections */ @AndroidEntryPoint class IncomingSharesFragment : MegaNodeBaseFragment() { - private val viewModel by activityViewModels() - private fun state() = viewModel.state.value + private fun incomingSharesState() = viewModel.state.value private lateinit var nodeController: NodeController + private val itemDecoration: PositionDividerItemDecoration by lazy(LazyThreadSafetyMode.NONE) { + PositionDividerItemDecoration(requireContext(), displayMetrics()) + } + /** * onCreateView */ @@ -69,12 +77,10 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { if (megaApi.rootNode == null) return null - val view = - if (managerActivity?.isList == true) getListView(inflater, container) - else getGridView(inflater, container) - - initAdapter() + val view = setupUI(inflater, container) + setupAdapter() selectNewlyAddedNodes() + switchViewType() return view } @@ -92,7 +98,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { * activateActionMode */ override fun activateActionMode() { - if (adapter?.isMultipleSelect == true) return + if (megaNodeAdapter?.isMultipleSelect == true) return super.activateActionMode() actionMode = @@ -103,8 +109,8 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { override fun itemClick(position: Int) { val actualPosition = position - 1 - val node = state().nodes.getOrNull(actualPosition)?.first - val shareData = state().nodes.getOrNull(actualPosition)?.second + val node = incomingSharesState().nodes.getOrNull(actualPosition)?.first + val shareData = incomingSharesState().nodes.getOrNull(actualPosition)?.second when { shareData?.isVerified == false -> { Intent(requireActivity(), AuthenticityCredentialsActivity::class.java).apply { @@ -120,9 +126,9 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { } } // select mode - adapter?.isMultipleSelect == true -> { - adapter?.toggleSelection(position) - val selectedNodes = adapter?.selectedNodes + megaNodeAdapter?.isMultipleSelect == true -> { + megaNodeAdapter?.toggleSelection(position) + val selectedNodes = megaNodeAdapter?.selectedNodes if ((selectedNodes?.size ?: 0) > 0) updateActionModeTitle() } @@ -144,17 +150,17 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { } override fun navigateToFolder(node: MegaNode) { - Timber.d("Is folder deep: %s", state().incomingTreeDepth) + Timber.d("Is folder deep: %s", incomingSharesState().incomingTreeDepth) val lastFirstVisiblePosition: Int = when { - managerActivity?.isList == true -> - mLayoutManager?.findFirstCompletelyVisibleItemPosition() ?: 0 + incomingSharesState().currentViewType == ViewType.LIST -> + recyclerView?.findFirstCompletelyVisibleItemPosition() ?: 0 - (recyclerView as NewGridRecyclerView).findFirstCompletelyVisibleItemPosition() == -1 -> - (recyclerView as NewGridRecyclerView).findFirstVisibleItemPosition() + recyclerView?.findFirstCompletelyVisibleItemPosition() == -1 -> + recyclerView?.findFirstVisibleItemPosition() ?: 0 else -> - (recyclerView as NewGridRecyclerView).findFirstCompletelyVisibleItemPosition() + recyclerView?.findFirstCompletelyVisibleItemPosition() ?: 0 } viewModel.pushToLastPositionStack(lastFirstVisiblePosition) @@ -165,10 +171,10 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { override fun onBackPressed(): Int { - if (adapter == null) + if (megaNodeAdapter == null) return 0 - if (managerActivity?.comesFromNotifications == true && managerActivity?.comesFromNotificationsLevel == state().incomingTreeDepth) { + if (managerActivity?.comesFromNotifications == true && managerActivity?.comesFromNotificationsLevel == incomingSharesState().incomingTreeDepth) { managerActivity?.restoreSharesAfterComingFromNotifications() return 4 } @@ -176,41 +182,33 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { managerActivity?.invalidateOptionsMenu() return when { - state().incomingTreeDepth == 1 -> { + incomingSharesState().incomingTreeDepth == 1 -> { Timber.d("deepBrowserTree==1") viewModel.resetIncomingTreeDepth() val lastVisiblePosition = viewModel.popLastPositionStack() lastVisiblePosition.takeIf { it > 0 }?.let { - if (managerActivity?.isList == true) - mLayoutManager?.scrollToPositionWithOffset(it, 0) - else - gridLayoutManager?.scrollToPositionWithOffset(it, 0) + recyclerView?.scrollToPosition(it) } recyclerView?.visibility = View.VISIBLE - emptyImageView?.visibility = View.GONE - emptyLinearLayout?.visibility = View.GONE + emptyListImageView?.visibility = View.GONE 3 } - state().incomingTreeDepth > 1 -> { + incomingSharesState().incomingTreeDepth > 1 -> { Timber.d("deepTree>1") - state().incomingParentHandle?.let { parentHandle -> + incomingSharesState().incomingParentHandle?.let { parentHandle -> recyclerView?.visibility = View.VISIBLE - emptyImageView?.visibility = View.GONE - emptyLinearLayout?.visibility = View.GONE + emptyListImageView?.visibility = View.GONE viewModel.decreaseIncomingTreeDepth(parentHandle) val lastVisiblePosition = viewModel.popLastPositionStack() lastVisiblePosition.takeIf { it > 0 }?.let { - if (managerActivity?.isList == true) - mLayoutManager?.scrollToPositionWithOffset(it, 0) - else - gridLayoutManager?.scrollToPositionWithOffset(it, 0) + recyclerView?.scrollToPosition(it) } } @@ -226,7 +224,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { } override fun showSortByPanel() { - val orderType = when (state().incomingTreeDepth) { + val orderType = when (incomingSharesState().incomingTreeDepth) { 0 -> ORDER_OTHERS else -> ORDER_CLOUD } @@ -236,9 +234,9 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { override val viewerFrom: Int = Constants.VIEWER_FROM_INCOMING_SHARES override val currentSharesTab: SharesTab = SharesTab.INCOMING_TAB override val sortOrder: SortOrder - get() = state().sortOrder + get() = incomingSharesState().sortOrder override val parentHandle: Long - get() = state().incomingHandle + get() = incomingSharesState().incomingHandle /** * Setup ViewModel observers @@ -269,11 +267,52 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { } } + sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, EventObserver { + showSortByPanel() + }) + viewLifecycleOwner.collectFlow(sortByHeaderViewModel.state) { state -> - val isList = state.viewType == ViewType.LIST - if (isList != viewModel.isList) { - viewModel.setIsList(isList) - initAdapter() + updateViewType(state.viewType) + } + } + + /** + * Updates the View Type of this Fragment + * + * Changing the View Type will cause the scroll position to be lost. To avoid that, only + * refresh the contents when the new View Type is different from the original View Type + * + * @param viewType The new View Type received from [SortByHeaderViewModel] + */ + private fun updateViewType(viewType: ViewType) { + if (viewType != incomingSharesState().currentViewType) { + viewModel.setCurrentViewType(viewType) + switchViewType() + } + } + + /** + * Switches how items in the [MegaNodeAdapter] are being displayed, based on the current + * [ViewType] in [IncomingSharesViewModel] + */ + private fun switchViewType() { + recyclerView?.run { + when (incomingSharesState().currentViewType) { + ViewType.LIST -> { + switchToLinear() + itemAnimator = Util.noChangeRecyclerViewItemAnimator() + if (itemDecorationCount == 0) addItemDecoration(itemDecoration) + megaNodeAdapter?.adapterType = MegaNodeAdapter.ITEM_VIEW_TYPE_LIST + } + ViewType.GRID -> { + switchBackToGrid() + itemAnimator = DefaultItemAnimator() + removeItemDecoration(itemDecoration) + (layoutManager as CustomizedGridLayoutManager).apply { + spanSizeLookup = megaNodeAdapter?.getSpanSizeLookup(spanCount) + } + megaNodeAdapter?.adapterType = MegaNodeAdapter.ITEM_VIEW_TYPE_GRID + } } } } @@ -286,35 +325,27 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { private fun updateNodes(nodes: List>) { val mutableListNodes = nodes.map { it.first } val mutableListShareData = nodes.map { it.second } - adapter?.setNodesWithShareData(mutableListNodes, mutableListShareData) + megaNodeAdapter?.setNodesWithShareData(mutableListNodes, mutableListShareData) } /** * Initialize the adapter */ - private fun initAdapter() { - if (adapter == null) { - adapter = MegaNodeAdapter( - requireActivity(), - this, - state().nodes.map { it.first }, - state().incomingHandle, - recyclerView, - Constants.INCOMING_SHARES_ADAPTER, - if (managerActivity?.isList == true) MegaNodeAdapter.ITEM_VIEW_TYPE_LIST - else MegaNodeAdapter.ITEM_VIEW_TYPE_GRID, - sortByHeaderViewModel - ) - } else { - adapter?.parentHandle = state().incomingHandle - adapter?.setListFragment(recyclerView) + private fun setupAdapter() { + megaNodeAdapter = MegaNodeAdapter( + requireActivity(), + this, + incomingSharesState().nodes.map { it.first }, + incomingSharesState().incomingHandle, + recyclerView, + Constants.INCOMING_SHARES_ADAPTER, + if (incomingSharesState().currentViewType == ViewType.LIST) MegaNodeAdapter.ITEM_VIEW_TYPE_LIST + else MegaNodeAdapter.ITEM_VIEW_TYPE_GRID, + sortByHeaderViewModel + ).also { + it.isMultipleSelect = false + recyclerView?.adapter = it } - if (managerActivity?.isList == false) - gridLayoutManager?.spanSizeLookup = - gridLayoutManager?.let { adapter?.getSpanSizeLookup(it.spanCount) } - - adapter?.isMultipleSelect = false - recyclerView?.adapter = adapter } /** @@ -330,16 +361,17 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { * If user navigates from notification about new nodes added to shared folder select all nodes and scroll to the first node in the list */ private fun selectNewlyAddedNodes() { - val positions = managerActivity?.getPositionsList(state().nodes.map { it.first }) + val positions = + managerActivity?.getPositionsList(incomingSharesState().nodes.map { it.first }) if (!positions.isNullOrEmpty()) { val firstPosition = Collections.min(positions) activateActionMode() for (position in positions) { - if (adapter?.isMultipleSelect == true) { - adapter?.toggleSelection(position) + if (megaNodeAdapter?.isMultipleSelect == true) { + megaNodeAdapter?.toggleSelection(position) } } - val selectedNodes = adapter?.selectedNodes + val selectedNodes = megaNodeAdapter?.selectedNodes if ((selectedNodes?.size ?: 0) > 0) { updateActionModeTitle() } @@ -356,16 +388,16 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { var textToShow: String? = null if (isInvalidHandle) { - emptyImageView?.let { + emptyListImageView?.let { setImageViewAlphaIfDark( requireContext(), it, ColorUtils.DARK_IMAGE_ALPHA ) } if (Util.isScreenInPortrait(requireContext())) { - emptyImageView?.setImageResource(R.drawable.incoming_shares_empty) + emptyListImageView?.setImageResource(R.drawable.incoming_shares_empty) } else { - emptyImageView?.setImageResource(R.drawable.incoming_empty_landscape) + emptyListImageView?.setImageResource(R.drawable.incoming_empty_landscape) } textToShow = requireContext().getString(R.string.context_empty_incoming) } @@ -377,7 +409,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { super.onPrepareActionMode(actionMode, menu) val control = CloudStorageOptionControlUtil.Control() - if (state().incomingTreeDepth == 0) { + if (incomingSharesState().incomingTreeDepth == 0) { control.leaveShare().setVisible(true).showAsAction = MenuItem.SHOW_AS_ACTION_ALWAYS } else if (areAllFileNodesAndNotTakenDown(selected)) { control.sendToChat().setVisible(true).showAsAction = MenuItem.SHOW_AS_ACTION_ALWAYS @@ -397,7 +429,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { } } - if (state().incomingTreeDepth > 0 && selected.isNotEmpty() && allHaveFullAccess( + if (incomingSharesState().incomingTreeDepth > 0 && selected.isNotEmpty() && allHaveFullAccess( selected ) ) { @@ -421,7 +453,7 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { } control.selectAll().isVisible = notAllNodesSelected() - control.trash().isVisible = (state().incomingTreeDepth > 0 + control.trash().isVisible = (incomingSharesState().incomingTreeDepth > 0 && allHaveFullAccess(selected)) CloudStorageOptionControlUtil.applyControl(menu, control) return true diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt index d9f65c76e88..8b8c7e41a68 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModel.kt @@ -15,6 +15,7 @@ import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates import mega.privacy.android.app.presentation.shares.incoming.model.IncomingSharesState import mega.privacy.android.domain.entity.user.UserChanges import mega.privacy.android.domain.entity.ShareData +import mega.privacy.android.domain.entity.preference.ViewType import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle @@ -100,10 +101,12 @@ class IncomingSharesViewModel @Inject constructor( } /** - * Set isList when update from MonitorViewType is received + * Updates the value of [IncomingSharesState.currentViewType] + * + * @param newViewType The new [ViewType] */ - fun setIsList(value: Boolean) { - isList = value + fun setCurrentViewType(newViewType: ViewType) { + _state.update { it.copy(currentViewType = newViewType) } } /** @@ -240,7 +243,7 @@ class IncomingSharesViewModel @Inject constructor( */ private suspend fun isInvalidHandle(handle: Long = _state.value.incomingHandle): Boolean { return handle - .takeUnless { it == -1L || it == INVALID_HANDLE } + .takeUnless { it == INVALID_HANDLE } ?.let { getNodeByHandle(it) == null } ?: true } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt index 25e7f2c3d7a..02dada63832 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/model/IncomingSharesState.kt @@ -2,22 +2,23 @@ package mega.privacy.android.app.presentation.shares.incoming.model import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder +import mega.privacy.android.domain.entity.preference.ViewType import nz.mega.sdk.MegaNode /** * Incoming shares UI state * - * @param incomingHandle current incoming shares handle - * @param incomingTreeDepth current incoming tree depth - * @param incomingParentHandle parent handle of the current incoming node - * @param nodes current list of nodes - * @param isInvalidHandle true if parent handle is invalid - * @param isLoading true if the nodes are loading - * @param sortOrder current sort order - * @param unverifiedIncomingShares List of unverified incoming [ShareData] - * @param unVerifiedIncomingNodeHandles List of unverified incoming node handles + * @property currentViewType serves as the original View Type + * @property incomingHandle current incoming shares handle + * @property incomingTreeDepth current incoming tree depth + * @property incomingParentHandle parent handle of the current incoming node + * @property nodes current list of nodes + * @property isInvalidHandle true if parent handle is invalid + * @property isLoading true if the nodes are loading + * @property sortOrder current sort order */ data class IncomingSharesState( + val currentViewType: ViewType = ViewType.LIST, val incomingHandle: Long = -1L, val incomingTreeDepth: Int = 0, val incomingParentHandle: Long? = null, diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt index 1cc0f9f0e1d..19ccdbc0fec 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt @@ -15,6 +15,8 @@ import androidx.lifecycle.repeatOnLifecycle import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import mega.privacy.android.app.R +import mega.privacy.android.app.components.PositionDividerItemDecoration +import mega.privacy.android.app.fragments.homepage.EventObserver import mega.privacy.android.app.main.adapters.MegaNodeAdapter import mega.privacy.android.app.presentation.manager.model.SharesTab import mega.privacy.android.app.presentation.manager.model.Tab @@ -27,6 +29,8 @@ import mega.privacy.android.app.utils.Constants.ORDER_CLOUD import mega.privacy.android.app.utils.MegaNodeUtil.areAllFileNodesAndNotTakenDown import mega.privacy.android.app.utils.MegaNodeUtil.areAllNotTakenDown import mega.privacy.android.app.utils.MegaNodeUtil.canMoveToRubbish +import mega.privacy.android.app.utils.Util +import mega.privacy.android.app.utils.displayMetrics import mega.privacy.android.domain.entity.SortOrder import nz.mega.sdk.MegaError import nz.mega.sdk.MegaNode @@ -43,6 +47,10 @@ class LinksFragment : MegaNodeBaseFragment() { private fun state() = viewModel.state.value + private val itemDecoration: PositionDividerItemDecoration by lazy(LazyThreadSafetyMode.NONE) { + PositionDividerItemDecoration(requireContext(), displayMetrics()) + } + /** * onCreateView */ @@ -57,9 +65,10 @@ class LinksFragment : MegaNodeBaseFragment() { return null } - val view = getListView(inflater, container) + val view = setupUI(inflater, container) initAdapter() + setupListViewConfiguration() return view } @@ -77,7 +86,7 @@ class LinksFragment : MegaNodeBaseFragment() { * activateActionMode */ override fun activateActionMode() { - if (adapter?.isMultipleSelect == true) return + if (megaNodeAdapter?.isMultipleSelect == true) return super.activateActionMode() actionMode = @@ -91,9 +100,9 @@ class LinksFragment : MegaNodeBaseFragment() { when { // select mode - adapter?.isMultipleSelect == true -> { - adapter?.toggleSelection(position) - val selectedNodes = adapter?.selectedNodes + megaNodeAdapter?.isMultipleSelect == true -> { + megaNodeAdapter?.toggleSelection(position) + val selectedNodes = megaNodeAdapter?.selectedNodes if ((selectedNodes?.size ?: 0) > 0) updateActionModeTitle() } @@ -111,7 +120,7 @@ class LinksFragment : MegaNodeBaseFragment() { override fun navigateToFolder(node: MegaNode) { Timber.d("Is folder deep: %s", state().linksTreeDepth) - mLayoutManager?.findFirstCompletelyVisibleItemPosition() + recyclerView?.findFirstCompletelyVisibleItemPosition() ?.let { viewModel.pushToLastPositionStack(it) } viewModel.increaseLinksTreeDepth(node.handle) recyclerView?.scrollToPosition(0) @@ -121,7 +130,7 @@ class LinksFragment : MegaNodeBaseFragment() { override fun onBackPressed(): Int { Timber.d("deepBrowserTree:%s", state().linksTreeDepth) - if (adapter == null) + if (megaNodeAdapter == null) return 0 return when { @@ -132,12 +141,11 @@ class LinksFragment : MegaNodeBaseFragment() { val lastVisiblePosition = viewModel.popLastPositionStack() lastVisiblePosition.takeIf { it > 0 }?.let { - mLayoutManager?.scrollToPositionWithOffset(it, 0) + recyclerView?.scrollToPosition(it) } recyclerView?.visibility = View.VISIBLE - emptyImageView?.visibility = View.GONE - emptyLinearLayout?.visibility = View.GONE + emptyListImageView?.visibility = View.GONE 3 } @@ -147,14 +155,13 @@ class LinksFragment : MegaNodeBaseFragment() { state().linksParentHandle?.let { parentHandle -> recyclerView?.visibility = View.VISIBLE - emptyImageView?.visibility = View.GONE - emptyLinearLayout?.visibility = View.GONE + emptyListImageView?.visibility = View.GONE viewModel.decreaseLinksTreeDepth(parentHandle) val lastVisiblePosition = viewModel.popLastPositionStack() lastVisiblePosition.takeIf { it > 0 }?.let { - mLayoutManager?.scrollToPositionWithOffset(it, 0) + recyclerView?.scrollToPosition(it) } } @@ -210,14 +217,29 @@ class LinksFragment : MegaNodeBaseFragment() { } } } + + sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, EventObserver { + showSortByPanel() + }) + } + + /** + * Sets up the Fragment to only run in List View configuration + */ + private fun setupListViewConfiguration() { + recyclerView?.run { + switchToLinear() + itemAnimator = Util.noChangeRecyclerViewItemAnimator() + if (itemDecorationCount == 0) addItemDecoration(itemDecoration) + } } /** * Initialize the adapter */ private fun initAdapter() { - if (adapter == null) { - adapter = MegaNodeAdapter( + if (megaNodeAdapter == null) { + megaNodeAdapter = MegaNodeAdapter( requireActivity(), this, state().nodes, @@ -228,12 +250,12 @@ class LinksFragment : MegaNodeBaseFragment() { sortByHeaderViewModel ) } else { - adapter?.parentHandle = state().linksHandle - adapter?.setListFragment(recyclerView) + megaNodeAdapter?.parentHandle = state().linksHandle + megaNodeAdapter?.setListFragment(recyclerView) } - adapter?.isMultipleSelect = false - recyclerView?.adapter = adapter + megaNodeAdapter?.isMultipleSelect = false + recyclerView?.adapter = megaNodeAdapter } /** @@ -243,7 +265,7 @@ class LinksFragment : MegaNodeBaseFragment() { */ private fun updateNodes(nodes: List) { val mutableListNodes = ArrayList(nodes) - adapter?.setNodes(mutableListNodes) + megaNodeAdapter?.setNodes(mutableListNodes) } /** @@ -263,13 +285,13 @@ class LinksFragment : MegaNodeBaseFragment() { private fun setEmptyView(isInvalidHandle: Boolean) { var textToShow: String? = null if (isInvalidHandle) { - emptyImageView?.let { + emptyListImageView?.let { setImageViewAlphaIfDark( requireContext(), it, ColorUtils.DARK_IMAGE_ALPHA ) } - emptyImageView?.setImageResource(R.drawable.ic_zero_data_public_links) + emptyListImageView?.setImageResource(R.drawable.ic_zero_data_public_links) textToShow = requireContext().getString(R.string.context_empty_links) } setFinalEmptyView(textToShow) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index 31269ee5877..ad9719c33a9 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -13,12 +13,16 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.DefaultItemAnimator import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch import mega.privacy.android.app.R import mega.privacy.android.app.arch.extensions.collectFlow -import mega.privacy.android.app.components.NewGridRecyclerView +import mega.privacy.android.app.components.CustomizedGridLayoutManager +import mega.privacy.android.app.components.PositionDividerItemDecoration +import mega.privacy.android.app.fragments.homepage.EventObserver +import mega.privacy.android.app.fragments.homepage.SortByHeaderViewModel import mega.privacy.android.app.main.adapters.MegaNodeAdapter import mega.privacy.android.app.presentation.contact.authenticitycredendials.AuthenticityCredentialsActivity import mega.privacy.android.app.presentation.manager.model.SharesTab @@ -31,8 +35,8 @@ import mega.privacy.android.app.utils.Constants.ORDER_OTHERS import mega.privacy.android.app.utils.MegaNodeUtil.areAllFileNodesAndNotTakenDown import mega.privacy.android.app.utils.MegaNodeUtil.areAllNotTakenDown import mega.privacy.android.app.utils.MegaNodeUtil.canMoveToRubbish -import mega.privacy.android.app.utils.StringResourcesUtils import mega.privacy.android.app.utils.Util +import mega.privacy.android.app.utils.displayMetrics import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.preference.ViewType @@ -50,7 +54,11 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { private val viewModel by activityViewModels() - private fun state() = viewModel.state.value + private fun outgoingSharesState() = viewModel.state.value + + private val itemDecoration: PositionDividerItemDecoration by lazy(LazyThreadSafetyMode.NONE) { + PositionDividerItemDecoration(requireContext(), displayMetrics()) + } /** * onCreateView @@ -65,11 +73,9 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { if (megaApi.rootNode == null) return null - val view = - if (managerActivity?.isList == true) getListView(inflater, container) - else getGridView(inflater, container) - - initAdapter() + val view = setupUI(inflater, container) + setupAdapter() + switchViewType() return view } @@ -87,7 +93,7 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { * activateActionMode */ override fun activateActionMode() { - if (adapter?.isMultipleSelect == true) return + if (megaNodeAdapter?.isMultipleSelect == true) return super.activateActionMode() actionMode = @@ -98,8 +104,8 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { override fun itemClick(position: Int) { val actualPosition = position - 1 - val node = state().nodes.getOrNull(actualPosition)?.first - val shareData = state().nodes.getOrNull(actualPosition)?.second + val node = outgoingSharesState().nodes.getOrNull(actualPosition)?.first + val shareData = outgoingSharesState().nodes.getOrNull(actualPosition)?.second when { shareData?.isPending == true -> { showCanNotVerifyContact(shareData.user) @@ -108,9 +114,9 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { openAuthenticityCredentials(shareData.user) } // select mode - adapter?.isMultipleSelect == true -> { - adapter?.toggleSelection(position) - val selectedNodes = adapter?.selectedNodes + megaNodeAdapter?.isMultipleSelect == true -> { + megaNodeAdapter?.toggleSelection(position) + val selectedNodes = megaNodeAdapter?.selectedNodes if ((selectedNodes?.size ?: 0) > 0) updateActionModeTitle() } @@ -132,17 +138,17 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { } override fun navigateToFolder(node: MegaNode) { - Timber.d("Is folder deep: %s", state().outgoingTreeDepth) + Timber.d("Is folder deep: %s", outgoingSharesState().outgoingTreeDepth) val lastFirstVisiblePosition: Int = when { - managerActivity?.isList == true -> - mLayoutManager?.findFirstCompletelyVisibleItemPosition() ?: 0 + outgoingSharesState().currentViewType == ViewType.LIST -> + recyclerView?.findFirstCompletelyVisibleItemPosition() ?: 0 - (recyclerView as NewGridRecyclerView).findFirstCompletelyVisibleItemPosition() == -1 -> - (recyclerView as NewGridRecyclerView).findFirstVisibleItemPosition() + recyclerView?.findFirstCompletelyVisibleItemPosition() == -1 -> + recyclerView?.findFirstVisibleItemPosition() ?: 0 else -> - (recyclerView as NewGridRecyclerView).findFirstCompletelyVisibleItemPosition() + recyclerView?.findFirstCompletelyVisibleItemPosition() ?: 0 } viewModel.pushToLastPositionStack(lastFirstVisiblePosition) @@ -154,47 +160,39 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { override fun onBackPressed(): Int { Timber.d("deepBrowserTree: %s", managerActivity?.deepBrowserTreeOutgoing) - if (adapter == null) + if (megaNodeAdapter == null) return 0 managerActivity?.invalidateOptionsMenu() return when { - state().outgoingTreeDepth == 1 -> { + outgoingSharesState().outgoingTreeDepth == 1 -> { Timber.d("deepBrowserTree==1") viewModel.resetOutgoingTreeDepth() val lastVisiblePosition = viewModel.popLastPositionStack() lastVisiblePosition.takeIf { it > 0 }?.let { - if (managerActivity?.isList == true) - mLayoutManager?.scrollToPositionWithOffset(it, 0) - else - gridLayoutManager?.scrollToPositionWithOffset(it, 0) + recyclerView?.scrollToPosition(it) } recyclerView?.visibility = View.VISIBLE - emptyImageView?.visibility = View.GONE - emptyLinearLayout?.visibility = View.GONE + emptyListImageView?.visibility = View.GONE 3 } - state().outgoingTreeDepth > 1 -> { + outgoingSharesState().outgoingTreeDepth > 1 -> { Timber.d("deepTree>1") - state().outgoingParentHandle?.let { parentHandle -> + outgoingSharesState().outgoingParentHandle?.let { parentHandle -> recyclerView?.visibility = View.VISIBLE - emptyImageView?.visibility = View.GONE - emptyLinearLayout?.visibility = View.GONE + emptyListImageView?.visibility = View.GONE viewModel.decreaseOutgoingTreeDepth(parentHandle) val lastVisiblePosition = viewModel.popLastPositionStack() lastVisiblePosition.takeIf { it > 0 }?.let { - if (managerActivity?.isList == true) - mLayoutManager?.scrollToPositionWithOffset(it, 0) - else - gridLayoutManager?.scrollToPositionWithOffset(it, 0) + recyclerView?.scrollToPosition(it) } } @@ -210,7 +208,7 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { } override fun showSortByPanel() { - val orderType = when (state().outgoingTreeDepth) { + val orderType = when (outgoingSharesState().outgoingTreeDepth) { 0 -> ORDER_OTHERS else -> ORDER_CLOUD } @@ -220,9 +218,9 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { override val viewerFrom: Int = Constants.VIEWER_FROM_OUTGOING_SHARES override val currentSharesTab: SharesTab = SharesTab.OUTGOING_TAB override val sortOrder: SortOrder - get() = state().sortOrder + get() = outgoingSharesState().sortOrder override val parentHandle: Long - get() = state().outgoingHandle + get() = outgoingSharesState().outgoingHandle /** * Setup ViewModel observers @@ -254,11 +252,52 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { } } + sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, EventObserver { + showSortByPanel() + }) + viewLifecycleOwner.collectFlow(sortByHeaderViewModel.state) { state -> - val isList = state.viewType == ViewType.LIST - if (isList != viewModel.isList) { - viewModel.setIsList(isList) - initAdapter() + updateViewType(state.viewType) + } + } + + /** + * Updates the View Type of this Fragment + * + * Changing the View Type will cause the scroll position to be lost. To avoid that, only + * refresh the contents when the new View Type is different from the original View Type + * + * @param viewType The new View Type received from [SortByHeaderViewModel] + */ + private fun updateViewType(viewType: ViewType) { + if (viewType != outgoingSharesState().currentViewType) { + viewModel.setCurrentViewType(viewType) + switchViewType() + } + } + + /** + * Switches how items in the [MegaNodeAdapter] are being displayed, based on the current + * [ViewType] in [OutgoingSharesViewModel] + */ + private fun switchViewType() { + recyclerView?.run { + when (outgoingSharesState().currentViewType) { + ViewType.LIST -> { + switchToLinear() + itemAnimator = Util.noChangeRecyclerViewItemAnimator() + if (itemDecorationCount == 0) addItemDecoration(itemDecoration) + megaNodeAdapter?.adapterType = MegaNodeAdapter.ITEM_VIEW_TYPE_LIST + } + ViewType.GRID -> { + switchBackToGrid() + itemAnimator = DefaultItemAnimator() + removeItemDecoration(itemDecoration) + (layoutManager as CustomizedGridLayoutManager).apply { + spanSizeLookup = megaNodeAdapter?.getSpanSizeLookup(spanCount) + } + megaNodeAdapter?.adapterType = MegaNodeAdapter.ITEM_VIEW_TYPE_GRID + } } } } @@ -271,35 +310,27 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { private fun updateNodes(nodes: List>) { val mutableListNodes = nodes.map { it.first } val mutableListShareData = nodes.map { it.second } - adapter?.setNodesWithShareData(mutableListNodes, mutableListShareData) + megaNodeAdapter?.setNodesWithShareData(mutableListNodes, mutableListShareData) } /** * Initialize the adapter */ - private fun initAdapter() { - if (adapter == null) { - adapter = MegaNodeAdapter( - requireActivity(), - this, - state().nodes.map { it.first }, - state().outgoingHandle, - recyclerView, - Constants.OUTGOING_SHARES_ADAPTER, - if (managerActivity?.isList == true) MegaNodeAdapter.ITEM_VIEW_TYPE_LIST - else MegaNodeAdapter.ITEM_VIEW_TYPE_GRID, - sortByHeaderViewModel - ) - } else { - adapter?.parentHandle = state().outgoingHandle - adapter?.setListFragment(recyclerView) + private fun setupAdapter() { + megaNodeAdapter = MegaNodeAdapter( + requireActivity(), + this, + outgoingSharesState().nodes.map { it.first }, + outgoingSharesState().outgoingHandle, + recyclerView, + Constants.OUTGOING_SHARES_ADAPTER, + if (outgoingSharesState().currentViewType == ViewType.LIST) MegaNodeAdapter.ITEM_VIEW_TYPE_LIST + else MegaNodeAdapter.ITEM_VIEW_TYPE_GRID, + sortByHeaderViewModel + ).also { + it.isMultipleSelect = false + recyclerView?.adapter = it } - if (managerActivity?.isList == false) - gridLayoutManager?.spanSizeLookup = - gridLayoutManager?.spanCount?.let { adapter?.getSpanSizeLookup(it) } - - adapter?.isMultipleSelect = false - recyclerView?.adapter = adapter } /** @@ -321,9 +352,9 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { if (isInvalidHandle) { if (Util.isScreenInPortrait(requireContext())) { - emptyImageView?.setImageResource(R.drawable.empty_outgoing_portrait) + emptyListImageView?.setImageResource(R.drawable.empty_outgoing_portrait) } else { - emptyImageView?.setImageResource(R.drawable.empty_outgoing_landscape) + emptyListImageView?.setImageResource(R.drawable.empty_outgoing_landscape) } textToShow = requireContext().getString(R.string.context_empty_outgoing) } @@ -356,12 +387,12 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { } val areAllNotTakenDown = selected.areAllNotTakenDown() if (areAllNotTakenDown) { - if (state().outgoingHandle == INVALID_HANDLE) { + if (outgoingSharesState().outgoingHandle == INVALID_HANDLE) { control.removeShare().setVisible(true).showAsAction = MenuItem.SHOW_AS_ACTION_ALWAYS } control.shareOut().setVisible(true).showAsAction = MenuItem.SHOW_AS_ACTION_ALWAYS - if (state().outgoingTreeDepth > 0) { + if (outgoingSharesState().outgoingTreeDepth > 0) { if (areAllFileNodesAndNotTakenDown(selected)) { control.sendToChat().setVisible(true).showAsAction = MenuItem.SHOW_AS_ACTION_ALWAYS @@ -415,7 +446,7 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { R.style.ThemeOverlay_Mega_MaterialAlertDialog ).setTitle(getString(R.string.shared_items_contact_not_in_contact_list_dialog_title)) .setMessage( - StringResourcesUtils.getString( + getString( R.string.shared_items_contact_not_in_contact_list_dialog_content, email ) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt index 589c193262e..b5031bb9522 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModel.kt @@ -11,9 +11,11 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.domain.usecase.GetNodeByHandle import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates +import mega.privacy.android.app.presentation.shares.incoming.model.IncomingSharesState import mega.privacy.android.app.presentation.shares.outgoing.model.OutgoingSharesState import mega.privacy.android.domain.entity.user.UserChanges import mega.privacy.android.domain.entity.ShareData +import mega.privacy.android.domain.entity.preference.ViewType import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetOthersSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle @@ -48,13 +50,6 @@ class OutgoingSharesViewModel @Inject constructor( /** public UI state */ val state: StateFlow = _state - /** - * Serves as the original View Type. - * When an update from MonitorViewType is received, this value is used to determine if the View Type changed & also updated - */ - var isList = true - private set - /** stack of scroll position for each depth */ private val lastPositionStack: Stack = Stack() @@ -85,10 +80,12 @@ class OutgoingSharesViewModel @Inject constructor( } /** - * Set isList when update from MonitorViewType is received + * Updates the value of [IncomingSharesState.currentViewType] + * + * @param newViewType The new [ViewType] */ - fun setIsList(value: Boolean) { - isList = value + fun setCurrentViewType(newViewType: ViewType) { + _state.update { it.copy(currentViewType = newViewType) } } /** @@ -226,7 +223,7 @@ class OutgoingSharesViewModel @Inject constructor( */ private suspend fun isInvalidHandle(handle: Long = _state.value.outgoingHandle): Boolean { return handle - .takeUnless { it == -1L || it == MegaApiJava.INVALID_HANDLE } + .takeUnless { it == MegaApiJava.INVALID_HANDLE } ?.let { getNodeByHandle(it) == null } ?: true } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt index 9aec436e80b..e6e1dcc4c4e 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/model/OutgoingSharesState.kt @@ -2,21 +2,23 @@ package mega.privacy.android.app.presentation.shares.outgoing.model import mega.privacy.android.domain.entity.ShareData import mega.privacy.android.domain.entity.SortOrder +import mega.privacy.android.domain.entity.preference.ViewType import nz.mega.sdk.MegaNode -import nz.mega.sdk.MegaShare /** * Outgoing shares UI state * - * @param outgoingHandle current outgoing shares handle - * @param outgoingTreeDepth current outgoing tree depth - * @param outgoingParentHandle parent handle of the current outgoing node - * @param nodes current list of nodes with his shareData associated if unverified or pending - * @param isInvalidHandle true if handle is invalid - * @param isLoading true if the nodes are loading - * @param sortOrder current sort order + * @property currentViewType serves as the original View Type + * @property outgoingHandle current outgoing shares handle + * @property outgoingTreeDepth current outgoing tree depth + * @property outgoingParentHandle parent handle of the current outgoing node + * @property nodes current list of nodes with his shareData associated if unverified or pending + * @property isInvalidHandle true if handle is invalid + * @property isLoading true if the nodes are loading + * @property sortOrder current sort order */ data class OutgoingSharesState( + val currentViewType: ViewType = ViewType.LIST, val outgoingHandle: Long = -1L, val outgoingTreeDepth: Int = 0, val outgoingParentHandle: Long? = null, diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt index f85e145bc3e..4ebc91c530d 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesViewModelTest.kt @@ -21,6 +21,7 @@ import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeChanges import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate +import mega.privacy.android.domain.entity.preference.ViewType import mega.privacy.android.domain.entity.user.UserUpdate import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetOthersSortOrder @@ -91,6 +92,7 @@ class IncomingSharesViewModelTest { fun `test that initial state is returned`() = runTest { underTest.state.test { val initial = awaitItem() + assertThat(initial.currentViewType).isEqualTo(ViewType.LIST) assertThat(initial.incomingHandle).isEqualTo(-1L) assertThat(initial.incomingTreeDepth).isEqualTo(0) assertThat(initial.nodes).isEmpty() @@ -519,4 +521,20 @@ class IncomingSharesViewModelTest { times(2) ).invoke(underTest.state.value.incomingHandle) } + + @Test + fun `test that the list view type is set when updating the current view type`() = + testSetCurrentViewType(ViewType.LIST) + + @Test + fun `test that the grid view type is set when updating the current view type`() = + testSetCurrentViewType(ViewType.GRID) + + private fun testSetCurrentViewType(expectedValue: ViewType) = runTest { + underTest.setCurrentViewType(expectedValue) + + underTest.state.test { + assertThat(awaitItem().currentViewType).isEqualTo(expectedValue) + } + } } diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt index a4f4a6dd30a..f4846515b51 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesViewModelTest.kt @@ -16,6 +16,7 @@ import mega.privacy.android.app.domain.usecase.GetOutgoingSharesChildrenNode import mega.privacy.android.app.presentation.shares.outgoing.OutgoingSharesViewModel import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.NodeUpdate +import mega.privacy.android.domain.entity.preference.ViewType import mega.privacy.android.domain.entity.user.UserUpdate import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetOthersSortOrder @@ -78,6 +79,7 @@ class OutgoingSharesViewModelTest { fun `test that initial state is returned`() = runTest { underTest.state.test { val initial = awaitItem() + assertThat(initial.currentViewType).isEqualTo(ViewType.LIST) assertThat(initial.outgoingHandle).isEqualTo(-1L) assertThat(initial.outgoingTreeDepth).isEqualTo(0) assertThat(initial.nodes).isEmpty() @@ -430,4 +432,20 @@ class OutgoingSharesViewModelTest { assertThat(awaitItem()).isEqualTo(expected) } } + + @Test + fun `test that the list view type is set when updating the current view type`() = + testSetCurrentViewType(ViewType.LIST) + + @Test + fun `test that the grid view type is set when updating the current view type`() = + testSetCurrentViewType(ViewType.GRID) + + private fun testSetCurrentViewType(expectedValue: ViewType) = runTest { + underTest.setCurrentViewType(expectedValue) + + underTest.state.test { + assertThat(awaitItem().currentViewType).isEqualTo(expectedValue) + } + } } From 00ca26366fce6b3e52869de95143257737b55b25 Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Tue, 28 Mar 2023 10:54:12 +0800 Subject: [PATCH 247/334] AP-125 AND - No warning for expired Business accounts. MR link: https://code.developers.mega.co.nz/mobile/android/android/-/merge_requests/4928 --- .../main/java/mega/privacy/android/app/main/ManagerActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt index 1e9f4614ab9..3be89a7bdc2 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt @@ -773,6 +773,7 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf return } updateAccountDetailsVisibleInfo() + checkInitialScreens() if (isBusinessAccount) { invalidateOptionsMenu() } From 7bdc3edd35f66ce73695784a47965cec5558b160 Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Tue, 28 Mar 2023 13:25:11 +0800 Subject: [PATCH 248/334] AND-16107 Crash: java.lang.NullPointerException(ImageViewerActivity) MR link: https://code.developers.mega.co.nz/mobile/android/android/-/merge_requests/4932 --- .../mega/privacy/android/app/imageviewer/ImageViewerActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/imageviewer/ImageViewerActivity.kt b/app/src/main/java/mega/privacy/android/app/imageviewer/ImageViewerActivity.kt index ae42718d83d..2a36a8406ec 100644 --- a/app/src/main/java/mega/privacy/android/app/imageviewer/ImageViewerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/imageviewer/ImageViewerActivity.kt @@ -646,7 +646,7 @@ class ImageViewerActivity : BaseActivity(), PermissionRequester, SnackbarShower putExtra(INTENT_EXTRA_KEY_FILE_NAME, nodeName) putExtra(INTENT_EXTRA_KEY_ADAPTER_TYPE, FROM_IMAGE_VIEWER) putExtra(INTENT_EXTRA_KEY_PARENT_NODE_HANDLE, - megaApi.getNodeByHandle(nodeHandle).parentHandle) + megaApi.getNodeByHandle(nodeHandle)?.parentHandle ?: INVALID_HANDLE) addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) } From 07bfbcb8a76512b20ab356150314f27a716f6a0b Mon Sep 17 00:00:00 2001 From: Kevin Ham Date: Wed, 29 Mar 2023 13:29:56 +1300 Subject: [PATCH 249/334] Pre-release v7.8 - Update MEGA SDK --- build.gradle | 2 +- sdk/src/main/jni/mega/sdk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 7e17188d2c6..ca37c301777 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230322.072949-rel' + megaSdkVersion = '20230328.030204-rel' } ext.shouldSuppressWarnings = { -> diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index be0f91bcc0d..412e2691ae1 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit be0f91bcc0df53d59d60ecfe03df8275eb6f5c8c +Subproject commit 412e2691ae10d4dfc66c11bcc48cff0254843722 From 18cc05cc8f4e14a3b6885baec8ed4387ff52d385 Mon Sep 17 00:00:00 2001 From: Daniel Oosthuizen Date: Wed, 29 Mar 2023 14:01:18 +1300 Subject: [PATCH 250/334] AND-16114 - Language order fix --- .../mega/privacy/android/app/BaseActivity.kt | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt index 53d655b8ab2..3073d8ce361 100644 --- a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt @@ -9,6 +9,7 @@ import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Handler +import android.os.LocaleList import android.os.Looper import android.text.TextUtils import android.util.DisplayMetrics @@ -663,6 +664,45 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque retryConnectionsAndSignalPresence() } + override fun attachBaseContext(newBase: Context?) { + /** + * When selecting a non supported locale and then a supported locale in the language settings + * causes a strange error in which the order of the two locales get randomly flipped. + * This causes some resources to be loaded in the supported language and others in the + * default language. I don't know what causes that to happen, but this code removes + * unsupported locales from the configuration as a measure to prevent the strange behaviour. + **/ + + val supportedLanguages = listOf( + "en", + "ar", + "de", + "es", + "fr", + "id", + "jt", + "ja", + "ko", + "nl", + "pl", + "pt", + "ro", + "ru", + "th", + "vi", + "zh", + ) + + newBase?.resources?.configuration?.let { + val locales = it.locales + val newLocales = (0 until locales.size()).mapNotNull { i -> + locales[i].takeIf { locale -> supportedLanguages.contains(locale.language) } + }.toTypedArray() + it.setLocales(LocaleList(*newLocales)) + } + super.attachBaseContext(newBase) + } + override fun onDestroy() { composite.clear() unregisterReceiver(sslErrorReceiver) @@ -923,7 +963,10 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque show() } MESSAGE_SNACKBAR_TYPE -> { - setAction(R.string.action_see, SnackbarNavigateOption(context = view.context, idChat = idChat)) + setAction( + R.string.action_see, + SnackbarNavigateOption(context = view.context, idChat = idChat) + ) show() } NOT_SPACE_SNACKBAR_TYPE -> { From 78ccead36fd01519cdb0463cef750716140d5493 Mon Sep 17 00:00:00 2001 From: Amr Date: Mon, 27 Mar 2023 12:34:42 +0800 Subject: [PATCH 251/334] fixed the wrong snackbar ui state --- .../TwoFactorAuthenticationActivity.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/twofactorauthentication/TwoFactorAuthenticationActivity.kt b/app/src/main/java/mega/privacy/android/app/presentation/twofactorauthentication/TwoFactorAuthenticationActivity.kt index da4cb2718fe..1448750e296 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/twofactorauthentication/TwoFactorAuthenticationActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/twofactorauthentication/TwoFactorAuthenticationActivity.kt @@ -664,12 +664,13 @@ class TwoFactorAuthenticationActivity : PasscodeActivity() { } private fun handleGetting2FACode(state: TwoFactorAuthenticationUIState) { - state.seed.takeIf { state.is2FAFetchCompleted }?.let { - seed = it - binding.qrProgressBar.visibility = View.VISIBLE - setSeed(seed.toSeedArray()) - - } ?: showSnackbar(getString(R.string.qr_seed_text_error)) + if (state.is2FAFetchCompleted) { + state.seed?.let { + this@TwoFactorAuthenticationActivity.seed = it + binding.qrProgressBar.visibility = View.VISIBLE + setSeed(it.toSeedArray()) + } ?: showSnackbar(getString(R.string.qr_seed_text_error)) + } } private fun handleIsMasterKeyExported(state: TwoFactorAuthenticationUIState) { From 55b8ae5959f6035586531c115193b6b2e91b4ddb Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Tue, 4 Apr 2023 09:13:29 +0700 Subject: [PATCH 252/334] AND-16161: Fix handleRootNodeAndHeartbeatState Intent.getAction NullPointerException (cherry picked from commit e7637feb9b3b4719394a101860a95d756d323646) --- .../java/mega/privacy/android/app/main/ManagerActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt index 3be89a7bdc2..cfc607252d6 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt @@ -1612,8 +1612,8 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf val isHeartBeatAlive: Boolean = MegaApplication.isIsHeartBeatAlive rootNode = megaApi.rootNode if (rootNode == null || LoginActivity.isBackFromLoginPage || isHeartBeatAlive) { - Timber.d("Action: %s", intent.action) - if (!handleRedirectIntentActions(intent.action)) { + Timber.d("Action: %s", intent?.action) + if (!handleRedirectIntentActions(intent?.action)) { refreshSession() } return true From 3e027586a3d5f348782fafdde4f7f184a02b0b65 Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Mon, 3 Apr 2023 09:45:07 +0700 Subject: [PATCH 253/334] AND-16146: Fix MegaApiFacade.getRubbishNode NullPointerException in Firebase (cherry picked from commit a6150b50eaeaaf89ffe316be41330749a296e9fd) --- .../main/java/mega/privacy/android/data/facade/MegaApiFacade.kt | 2 +- .../mega/privacy/android/data/gateway/api/MegaApiGateway.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt index 9ec650d6177..0bde0c71c17 100644 --- a/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt +++ b/data/src/main/java/mega/privacy/android/data/facade/MegaApiFacade.kt @@ -689,7 +689,7 @@ internal class MegaApiFacade @Inject constructor( override suspend fun getOutShares(megaNode: MegaNode): List = megaApi.getOutShares(megaNode) - override suspend fun getRubbishNode(): MegaNode = megaApi.rubbishNode + override suspend fun getRubbishNode(): MegaNode? = megaApi.rubbishNode override fun createSet(name: String, listener: MegaRequestListenerInterface) = megaApi.createSet(name, listener) diff --git a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt index ce1bc78655f..e29d3c7bcf2 100644 --- a/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt +++ b/data/src/main/java/mega/privacy/android/data/gateway/api/MegaApiGateway.kt @@ -1143,7 +1143,7 @@ interface MegaApiGateway { * * @return Rubbish node of the account. */ - suspend fun getRubbishNode(): MegaNode + suspend fun getRubbishNode(): MegaNode? /** * Create a new MegaSet item From 715135b700c6fd725eb9711fc045c6978a7eee9b Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Tue, 4 Apr 2023 16:24:37 +0700 Subject: [PATCH 254/334] AND-16162: Fix LoginActivity.showFragment FragmentManager is already executing transactions --- .../android/app/presentation/login/LoginActivity.kt | 5 ++++- .../android/app/presentation/login/LoginViewModel.kt | 8 ++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/login/LoginActivity.kt b/app/src/main/java/mega/privacy/android/app/presentation/login/LoginActivity.kt index 6ed8e91e65b..5e0f279723e 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/login/LoginActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/login/LoginActivity.kt @@ -145,7 +145,10 @@ class LoginActivity : BaseActivity(), MegaRequestListenerInterface { with(uiState) { when { isPendingToFinishActivity -> finish() - isPendingToShowFragment != null -> showFragment(isPendingToShowFragment.toConstant()) + isPendingToShowFragment != null -> { + showFragment(isPendingToShowFragment.toConstant()) + viewModel.markHandledPendingToShowFragment() + } } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/login/LoginViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/login/LoginViewModel.kt index ba603f4f01e..a05c258d8e1 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/login/LoginViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/login/LoginViewModel.kt @@ -182,6 +182,14 @@ class LoginViewModel @Inject constructor( fun setTourAsPendingFragment() = _state.update { state -> state.copy(isPendingToShowFragment = LoginFragmentType.Tour) } + /** + * Mark handled pending to show fragment + * + */ + fun markHandledPendingToShowFragment() { + _state.update { state -> state.copy(isPendingToShowFragment = null) } + } + /** * Updates state with a new intentAction. * From 6def0893f15f7f628276815a7c9ccef42a2e6230 Mon Sep 17 00:00:00 2001 From: Daniel Oosthuizen Date: Tue, 4 Apr 2023 17:24:53 +1200 Subject: [PATCH 255/334] AND-16164 - Ensure non null locale --- .../mega/privacy/android/app/BaseActivity.kt | 40 +----------- .../chat/dialog/AskForDisplayOverActivity.kt | 6 ++ .../locale/SupportedLanguageContextWrapper.kt | 62 +++++++++++++++++++ 3 files changed, 71 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/mega/privacy/android/app/presentation/locale/SupportedLanguageContextWrapper.kt diff --git a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt index 3073d8ce361..6deb784b24c 100644 --- a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt @@ -9,7 +9,6 @@ import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Handler -import android.os.LocaleList import android.os.Looper import android.text.TextUtils import android.util.DisplayMetrics @@ -58,6 +57,7 @@ import mega.privacy.android.app.meeting.activity.MeetingActivity import mega.privacy.android.app.myAccount.MyAccountActivity import mega.privacy.android.app.namecollision.data.NameCollision import mega.privacy.android.app.presentation.billing.BillingViewModel +import mega.privacy.android.app.presentation.locale.SupportedLanguageContextWrapper import mega.privacy.android.app.presentation.login.LoginActivity import mega.privacy.android.app.presentation.verification.SMSVerificationActivity import mega.privacy.android.app.psa.Psa @@ -665,44 +665,10 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque } override fun attachBaseContext(newBase: Context?) { - /** - * When selecting a non supported locale and then a supported locale in the language settings - * causes a strange error in which the order of the two locales get randomly flipped. - * This causes some resources to be loaded in the supported language and others in the - * default language. I don't know what causes that to happen, but this code removes - * unsupported locales from the configuration as a measure to prevent the strange behaviour. - **/ - - val supportedLanguages = listOf( - "en", - "ar", - "de", - "es", - "fr", - "id", - "jt", - "ja", - "ko", - "nl", - "pl", - "pt", - "ro", - "ru", - "th", - "vi", - "zh", - ) - - newBase?.resources?.configuration?.let { - val locales = it.locales - val newLocales = (0 until locales.size()).mapNotNull { i -> - locales[i].takeIf { locale -> supportedLanguages.contains(locale.language) } - }.toTypedArray() - it.setLocales(LocaleList(*newLocales)) - } - super.attachBaseContext(newBase) + super.attachBaseContext(SupportedLanguageContextWrapper(newBase)) } + override fun onDestroy() { composite.clear() unregisterReceiver(sslErrorReceiver) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/chat/dialog/AskForDisplayOverActivity.kt b/app/src/main/java/mega/privacy/android/app/presentation/chat/dialog/AskForDisplayOverActivity.kt index 58c3effb175..2641413d3f2 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/chat/dialog/AskForDisplayOverActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/chat/dialog/AskForDisplayOverActivity.kt @@ -1,5 +1,6 @@ package mega.privacy.android.app.presentation.chat.dialog +import android.content.Context import android.content.Intent import android.graphics.Color import android.net.Uri @@ -18,6 +19,7 @@ import mega.privacy.android.app.R import mega.privacy.android.app.arch.extensions.collectFlow import mega.privacy.android.app.presentation.chat.dialog.view.AskForDisplayOverDialog import mega.privacy.android.app.presentation.extensions.isDarkMode +import mega.privacy.android.app.presentation.locale.SupportedLanguageContextWrapper import mega.privacy.android.core.ui.theme.AndroidTheme import mega.privacy.android.domain.entity.ThemeMode import mega.privacy.android.domain.usecase.GetThemeMode @@ -102,4 +104,8 @@ class AskForDisplayOverActivity : ComponentActivity() { super.onDestroy() viewModel.onDestroy() } + + override fun attachBaseContext(newBase: Context?) { + super.attachBaseContext(SupportedLanguageContextWrapper(newBase)) + } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/locale/SupportedLanguageContextWrapper.kt b/app/src/main/java/mega/privacy/android/app/presentation/locale/SupportedLanguageContextWrapper.kt new file mode 100644 index 00000000000..ef5834fa80e --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/presentation/locale/SupportedLanguageContextWrapper.kt @@ -0,0 +1,62 @@ +package mega.privacy.android.app.presentation.locale + +import android.content.Context +import android.content.ContextWrapper +import android.os.LocaleList + +/** + * Supported language context wrapper + * + * @constructor + * + * @param base + */ +class SupportedLanguageContextWrapper(base: Context?) : ContextWrapper(base) { + private val supportedLanguages = listOf( + "en", + "ar", + "de", + "es", + "fr", + "id", + "jt", + "ja", + "ko", + "nl", + "pl", + "pt", + "ro", + "ru", + "th", + "vi", + "zh", + ) + + override fun attachBaseContext(newBase: Context?) { + /** + * When selecting a non supported locale and then a supported locale in the language settings + * causes a strange error in which the order of the two locales get randomly flipped. + * This causes some resources to be loaded in the supported language and others in the + * default language. I don't know what causes that to happen, but this code removes + * unsupported locales from the configuration as a measure to prevent the strange behaviour. + **/ + + + newBase?.resources?.configuration?.let { configuration -> + val locales = configuration.locales + LocaleList(*getSupportedLocales(locales, supportedLanguages)) + .takeUnless { it.isEmpty } + ?.let { + configuration.setLocales(it) + } + } + super.attachBaseContext(newBase) + } + + private fun getSupportedLocales( + locales: LocaleList, + supportedLanguages: List, + ) = (0 until locales.size()).mapNotNull { i -> + locales[i].takeIf { locale -> supportedLanguages.contains(locale.language) } + }.toTypedArray() +} \ No newline at end of file From 3cd2df00f363f58ede5c1d424e78d8217d53d72e Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Tue, 4 Apr 2023 16:10:31 +0700 Subject: [PATCH 256/334] AND-16169: Blank screen when share media to application (cherry picked from commit c6dc0adbed9f5d926c93335d2befcf291eae4ba6) --- .../mega/privacy/android/app/main/FileExplorerActivity.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/FileExplorerActivity.kt b/app/src/main/java/mega/privacy/android/app/main/FileExplorerActivity.kt index 389d7e33495..5be318d3cb0 100644 --- a/app/src/main/java/mega/privacy/android/app/main/FileExplorerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/FileExplorerActivity.kt @@ -2055,9 +2055,7 @@ class FileExplorerActivity : TransfersManagementActivity(), MegaRequestListenerI credentials = UserCredentials(lastEmail, gSession, "", "", myUserHandle) dbH.saveCredentials(credentials ?: return) binding.fileLoggingInLayout.isVisible = false - if (isLoggingIn) { - afterLoginAndFetch() - } + afterLoginAndFetch() isLoggingIn = false } } From f38c74454a20cf914d66f5855a6063ec240cb835 Mon Sep 17 00:00:00 2001 From: Atiqur Rahman Date: Tue, 4 Apr 2023 15:19:20 +0600 Subject: [PATCH 257/334] AND-16165: Filter if filetypeInfo is VideoFileTypeInfo --- .../privacy/android/data/repository/DefaultPhotosRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/mega/privacy/android/data/repository/DefaultPhotosRepository.kt b/data/src/main/java/mega/privacy/android/data/repository/DefaultPhotosRepository.kt index a3d4536ec6c..fb8c6f093ea 100644 --- a/data/src/main/java/mega/privacy/android/data/repository/DefaultPhotosRepository.kt +++ b/data/src/main/java/mega/privacy/android/data/repository/DefaultPhotosRepository.kt @@ -306,7 +306,7 @@ internal class DefaultPhotosRepository @Inject constructor( */ private suspend fun mapPhotoNodesToVideos(megaNodes: List): List { return megaNodes.filter { - it.isValidPhotoNode() + it.isValidPhotoNode() && fileTypeInfoMapper(it) is VideoFileTypeInfo }.map { megaNode -> mapMegaNodeToVideo(megaNode) } From 616970eb929695a884e1f959823efa3c3b69d5ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raquel=20Garc=C3=ADa=20Chico?= Date: Tue, 4 Apr 2023 12:17:36 +0200 Subject: [PATCH 258/334] MEET-2245 NullPointerException: Caused by MegaChatRoom.isActive() must not be null app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java --- .../mega/privacy/android/app/main/megachat/ChatActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java b/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java index 6f04fca4580..3b4c6247635 100644 --- a/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java +++ b/app/src/main/java/mega/privacy/android/app/main/megachat/ChatActivity.java @@ -2085,7 +2085,7 @@ private void collectFlows() { ScheduledMeetingStatus schedMeetStatus = chatState.getScheduledMeetingStatus(); - if (chatRoom.isActive() && !chatRoom.isArchived() && schedMeetStatus != null && (schedMeetStatus == ScheduledMeetingStatus.NotStarted || + if (chatRoom != null && chatRoom.isActive() && !chatRoom.isArchived() && schedMeetStatus != null && (schedMeetStatus == ScheduledMeetingStatus.NotStarted || schedMeetStatus == ScheduledMeetingStatus.NotJoined)) { startOrJoinMeetingBanner.setText(schedMeetStatus == ScheduledMeetingStatus.NotStarted ? R.string.meetings_chat_room_start_scheduled_meeting_option : From 3a0e6b60d623b1725a3549c4a06eb9bbbeca6845 Mon Sep 17 00:00:00 2001 From: Robin Shi Date: Wed, 5 Apr 2023 08:50:25 +0800 Subject: [PATCH 259/334] Increase version to 7.8.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ca37c301777..a98f23046bf 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ task clean(type: Delete) { // Define versions in a single place ext { // App - appVersion = "7.8" + appVersion = "7.8.1" // Sdk and tools compileSdkVersion = 33 From 1e4369853f94fa6ad1bfb5de01d21020fb9faea9 Mon Sep 17 00:00:00 2001 From: Robin Shi Date: Thu, 6 Apr 2023 12:04:46 +1200 Subject: [PATCH 260/334] AND-16177 correct app language support for Chinese and Italian (cherry picked from commit c9523b1989eb8babf62d1751765af9ac094a4a75) --- app/build.gradle | 2 +- app/src/main/res/xml/locales_config.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c1d4e943811..1ab114e15ba 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -64,7 +64,7 @@ android { testInstrumentationRunner "test.mega.privacy.android.app.HiltTestRunner" - resourceConfigurations += ["en", "ar", "de", "es", "fr", "id", "jt", "ja", "ko", "nl", "pl", "pt", "ro", "ru", "th", "vi", "zh-rCH", "zh-rTW"] + resourceConfigurations += ["en", "ar", "de", "es", "fr", "id", "it", "ja", "ko", "nl", "pl", "pt", "ro", "ru", "th", "vi", "zh-rCN", "zh-rTW"] } sourceSets { diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml index c2215ed6071..f8e1b9fb3cf 100644 --- a/app/src/main/res/xml/locales_config.xml +++ b/app/src/main/res/xml/locales_config.xml @@ -6,7 +6,7 @@ - + From 749c1208aecc16dcd81aa2cde812488254c79843 Mon Sep 17 00:00:00 2001 From: Pau Dominkovics Coll Date: Thu, 6 Apr 2023 15:57:55 +1200 Subject: [PATCH 261/334] AND-16065: fix race condition that was hiding available offline switch --- .../fileinfo/FileInfoViewModel.kt | 10 +-- .../fileinfo/model/FileInfoViewState.kt | 22 +++-- .../fileinfo/model/FileInfoViewStateTest.kt | 80 +++++++++++++++++-- 3 files changed, 94 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoViewModel.kt index 5e8477ccedc..8bd4f061af2 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoViewModel.kt @@ -507,7 +507,7 @@ class FileInfoViewModel @Inject constructor( getContactItemFromInShareFolder(folderNode = it, skipCache = false) } val isNodeInRubbish = isNodeInRubbish(typedNode.id.longValue) - uiState.copy( + uiState.copyWithTypedNode( typedNode = typedNode, ).copy( iconResource = getNodeIcon(typedNode, _uiState.value.origin.fromShares), @@ -543,7 +543,7 @@ class FileInfoViewModel @Inject constructor( updateState { //we need to update the typedNode to get changes, for instance, on outgoing shares typedNode = getNodeById(typedNode.id) - it.copy( + it.copyWithTypedNode( typedNode = typedNode ).copy( iconResource = getNodeIcon(typedNode, _uiState.value.origin.fromShares), @@ -565,14 +565,14 @@ class FileInfoViewModel @Inject constructor( updateState { //we need to update the typedNode to get changes in timeStamps typedNode = getNodeById(typedNode.id) - it.copy(typedNode = typedNode) + it.copyWithTypedNode(typedNode = typedNode) } } private fun updateFolderTreeInfo() { (typedNode as? TypedFolderNode)?.let { folder -> updateState { - it.copy(folderTreeInfo = getFolderTreeInfo(folder)) + it.copyWithFolderTreeInfo(folderTreeInfo = getFolderTreeInfo(folder)) } } } @@ -603,7 +603,7 @@ class FileInfoViewModel @Inject constructor( private fun updateTitle() { updateState { typedNode = getNodeById(typedNode.id) - it.copy(typedNode = typedNode) + it.copyWithTypedNode(typedNode = typedNode) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/model/FileInfoViewState.kt b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/model/FileInfoViewState.kt index c1d55e82b24..f98b8f4e090 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/model/FileInfoViewState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/model/FileInfoViewState.kt @@ -8,7 +8,6 @@ import mega.privacy.android.domain.entity.FolderTreeInfo import mega.privacy.android.domain.entity.contacts.ContactItem import mega.privacy.android.domain.entity.node.FileNode import mega.privacy.android.domain.entity.node.TypedFileNode -import mega.privacy.android.domain.entity.node.TypedFolderNode import mega.privacy.android.domain.entity.node.TypedNode import mega.privacy.android.domain.entity.shares.AccessPermission import nz.mega.sdk.MegaShare @@ -84,16 +83,14 @@ data class FileInfoViewState( /** * Creates a copy of this view state with the info that can be extracted directly from typedNode */ - fun copy(typedNode: TypedNode) = this.copy( + fun copyWithTypedNode(typedNode: TypedNode) = this.copy( title = typedNode.name, isFile = typedNode is FileNode, - sizeInBytes = folderTreeInfo?.let { - it.totalCurrentSizeInBytes + it.sizeOfPreviousVersionsInBytes - } ?: (typedNode as? FileNode)?.size ?: 0, - isAvailableOfflineAvailable = if (typedNode is TypedFolderNode) { - (folderTreeInfo?.numberOfFiles ?: 0) > 0 - } else { + sizeInBytes = (typedNode as? FileNode)?.size ?: this.sizeInBytes, + isAvailableOfflineAvailable = if (typedNode is FileNode) { true + } else { + this.isAvailableOfflineAvailable }, isTakenDown = typedNode.isTakenDown, isExported = typedNode.exportedData != null, @@ -105,6 +102,15 @@ data class FileInfoViewState( hasPreview = (typedNode as? TypedFileNode)?.hasPreview == true ) + /** + * Creates a copy of this view state with the info that can be extracted directly from folderTreeInfo + */ + fun copyWithFolderTreeInfo(folderTreeInfo: FolderTreeInfo) = this.copy( + folderTreeInfo = folderTreeInfo, + sizeInBytes = folderTreeInfo.totalCurrentSizeInBytes + folderTreeInfo.sizeOfPreviousVersionsInBytes, + isAvailableOfflineAvailable = folderTreeInfo.numberOfFiles > 0, + ) + /** * determines if the file history versions should be shown */ diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/fileinfo/model/FileInfoViewStateTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/fileinfo/model/FileInfoViewStateTest.kt index 8d6fc96f637..009b56377ee 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/fileinfo/model/FileInfoViewStateTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/fileinfo/model/FileInfoViewStateTest.kt @@ -3,16 +3,17 @@ package test.mega.privacy.android.app.presentation.fileinfo.model import com.google.common.truth.Truth import mega.privacy.android.app.presentation.fileinfo.model.FileInfoViewState import mega.privacy.android.app.presentation.fileinfo.model.FileInfoViewState.Companion.MAX_NUMBER_OF_CONTACTS_IN_LIST +import mega.privacy.android.domain.entity.FolderTreeInfo +import mega.privacy.android.domain.entity.node.TypedFileNode +import mega.privacy.android.domain.entity.node.TypedFolderNode import nz.mega.sdk.MegaShare -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.junit.MockitoJUnitRunner +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.ValueSource import org.mockito.kotlin.mock -@RunWith(MockitoJUnitRunner::class) class FileInfoViewStateTest { - private lateinit var underTest: FileInfoViewState @Test @@ -30,4 +31,73 @@ class FileInfoViewStateTest { Truth.assertThat(underTest.outSharesCoerceMax.size) .isEqualTo(MAX_NUMBER_OF_CONTACTS_IN_LIST) } + + @Test + fun `test that when typed node is updated with a file node then available offline is updated to true`() { + underTest = FileInfoViewState(isAvailableOfflineAvailable = false) + val result = underTest.copyWithTypedNode(typedNode = mockFile()) + Truth.assertThat(result.isAvailableOfflineAvailable).isEqualTo(true) + } + + @Test + fun `test that when typed node is updated with a file node then size is updated to file size`() { + underTest = FileInfoViewState() + val result = underTest.copyWithTypedNode(typedNode = mockFile()) + Truth.assertThat(result.sizeInBytes).isEqualTo(FILE_SIZE) + } + + @ParameterizedTest(name = "initial value: {0}") + @ValueSource(booleans = [true, false]) + fun `test that when typed node is updated with a folder node then available offline is not updated`( + initialValue: Boolean, + ) { + underTest = FileInfoViewState(isAvailableOfflineAvailable = initialValue) + val result = underTest.copyWithTypedNode(typedNode = mockFolder()) + Truth.assertThat(result.isAvailableOfflineAvailable).isEqualTo(initialValue) + } + + @Test + fun `test that when typed node is updated with a folder node then size is not updated`() { + underTest = FileInfoViewState() + val result = underTest.copyWithTypedNode(typedNode = mockFolder()) + Truth.assertThat(result.sizeInBytes).isEqualTo(0L) + } + + @Test + fun `test that when folder tree info is updated then size is updated to totalCurrentSizeInBytes plus sizeOfPreviousVersionsInBytes`() { + underTest = FileInfoViewState() + val result = underTest.copyWithFolderTreeInfo(mockFolderTreeInfo()) + Truth.assertThat(result.sizeInBytes).isEqualTo(CURRENT_SIZE + PREVIOUS_SIZE) + } + + @ParameterizedTest(name = "folder empty: {0}") + @ValueSource(booleans = [true, false]) + fun `test that when folder tree info is updated then isAvailableOffline is updated to folder is empty or not`( + empty: Boolean, + ) { + underTest = FileInfoViewState() + val result = underTest.copyWithFolderTreeInfo(mockFolderTreeInfo(empty)) + Truth.assertThat(result.isAvailableOfflineAvailable).isEqualTo(!empty) + } + + private fun mockFolder() = mock { + on { name }.thenReturn("Node") + } + + private fun mockFile() = mock { + on { name }.thenReturn("Node") + on { size }.thenReturn(FILE_SIZE) + } + + private fun mockFolderTreeInfo(empty: Boolean = true) = mock { + on { totalCurrentSizeInBytes }.thenReturn(CURRENT_SIZE) + on { sizeOfPreviousVersionsInBytes }.thenReturn(PREVIOUS_SIZE) + on { numberOfFiles }.thenReturn(if (empty) 0 else 1) + } + + companion object { + private const val FILE_SIZE = 123L + private const val CURRENT_SIZE = 1234L + private const val PREVIOUS_SIZE = 12345L + } } \ No newline at end of file From d661d92c3d1ce19ddaf072b9f7527a0f94e164ee Mon Sep 17 00:00:00 2001 From: Amr Mohsen Date: Fri, 7 Apr 2023 21:20:23 +1200 Subject: [PATCH 262/334] AND-16187: Fixed Failed TC on TestRail --- .../privacy/android/app/listeners/RemoveListener.kt | 5 ++++- .../app/main/listeners/MultipleRequestListener.java | 10 ++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/listeners/RemoveListener.kt b/app/src/main/java/mega/privacy/android/app/listeners/RemoveListener.kt index d16697e5c26..03db7c8137d 100644 --- a/app/src/main/java/mega/privacy/android/app/listeners/RemoveListener.kt +++ b/app/src/main/java/mega/privacy/android/app/listeners/RemoveListener.kt @@ -61,7 +61,10 @@ class RemoveListener( Intent(BroadcastConstants.BROADCAST_ACTION_SHOW_SNACKBAR).run { putExtra( BroadcastConstants.SNACKBAR_TEXT, - context.getString(R.string.share_left) + context.resources.getQuantityString( + R.plurals.shared_items_incoming_shares_snackbar_leaving_shares_success, + 1, 1 + ) ) MegaApplication.getInstance().sendBroadcast(this) } diff --git a/app/src/main/java/mega/privacy/android/app/main/listeners/MultipleRequestListener.java b/app/src/main/java/mega/privacy/android/app/main/listeners/MultipleRequestListener.java index 1cdc51c6adb..61cb8ab4b30 100644 --- a/app/src/main/java/mega/privacy/android/app/main/listeners/MultipleRequestListener.java +++ b/app/src/main/java/mega/privacy/android/app/main/listeners/MultipleRequestListener.java @@ -5,9 +5,7 @@ import static mega.privacy.android.app.utils.Constants.MULTIPLE_SEND_RUBBISH; import static mega.privacy.android.app.utils.DBUtil.resetAccountDetailsTimeStamp; import static mega.privacy.android.app.utils.Util.showSnackbar; - import android.content.Context; - import mega.privacy.android.app.R; import mega.privacy.android.app.main.megachat.ChatActivity; import mega.privacy.android.app.main.megachat.NodeAttachmentHistoryActivity; @@ -109,21 +107,21 @@ public void onRequestFinish(MegaApiJava api, MegaRequest request, MegaError e) { message = context.getResources(). getQuantityString( R.plurals.shared_items_incoming_shares_snackbar_leaving_shares_fail, - error); + error, error); } else { String correctlyLeft = context.getResources().getQuantityString( R.plurals.shared_items_incoming_shares_snackbar_leaving_shares_success_concat, - max_items - error); + max_items - error, max_items - error); String notLeft = context.getResources().getQuantityString( R.plurals.shared_items_incoming_shares_snackbar_leaving_shares_fail_concat, - error); + error, error); message = correctlyLeft.concat(notLeft); } } } else { message = context.getResources(). getQuantityString(R.plurals.shared_items_incoming_shares_snackbar_leaving_shares_success, - max_items); + max_items, max_items); } } From 975c8410664688fdfc13207d06b1a04a6c98e1cb Mon Sep 17 00:00:00 2001 From: Daniel Oosthuizen Date: Tue, 11 Apr 2023 11:54:25 +1200 Subject: [PATCH 263/334] AND-16114 - Fix language regression --- .../mega/privacy/android/app/BaseActivity.kt | 2 +- .../chat/dialog/AskForDisplayOverActivity.kt | 2 +- .../locale/SupportedLanguageContextWrapper.kt | 98 ++++++++++--------- 3 files changed, 56 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt index 94c908244b1..0fd6d9c7695 100644 --- a/app/src/main/java/mega/privacy/android/app/BaseActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/BaseActivity.kt @@ -665,7 +665,7 @@ open class BaseActivity : AppCompatActivity(), ActivityLauncher, PermissionReque } override fun attachBaseContext(newBase: Context?) { - super.attachBaseContext(SupportedLanguageContextWrapper(newBase)) + super.attachBaseContext(SupportedLanguageContextWrapper.wrap(newBase)) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/chat/dialog/AskForDisplayOverActivity.kt b/app/src/main/java/mega/privacy/android/app/presentation/chat/dialog/AskForDisplayOverActivity.kt index df7aa239dbd..88103319ba9 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/chat/dialog/AskForDisplayOverActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/chat/dialog/AskForDisplayOverActivity.kt @@ -105,6 +105,6 @@ class AskForDisplayOverActivity : AppCompatActivity() { } override fun attachBaseContext(newBase: Context?) { - super.attachBaseContext(SupportedLanguageContextWrapper(newBase)) + super.attachBaseContext(SupportedLanguageContextWrapper.wrap(newBase)) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/locale/SupportedLanguageContextWrapper.kt b/app/src/main/java/mega/privacy/android/app/presentation/locale/SupportedLanguageContextWrapper.kt index ef5834fa80e..408fd547794 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/locale/SupportedLanguageContextWrapper.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/locale/SupportedLanguageContextWrapper.kt @@ -11,52 +11,62 @@ import android.os.LocaleList * * @param base */ -class SupportedLanguageContextWrapper(base: Context?) : ContextWrapper(base) { - private val supportedLanguages = listOf( - "en", - "ar", - "de", - "es", - "fr", - "id", - "jt", - "ja", - "ko", - "nl", - "pl", - "pt", - "ro", - "ru", - "th", - "vi", - "zh", - ) - - override fun attachBaseContext(newBase: Context?) { +class SupportedLanguageContextWrapper private constructor(base: Context?) : ContextWrapper(base) { + + companion object { + + private val supportedLanguages = listOf( + "en", + "ar", + "de", + "es", + "fr", + "id", + "it", + "ja", + "ko", + "nl", + "pl", + "pt", + "ro", + "ru", + "th", + "vi", + "zh", + ) + /** - * When selecting a non supported locale and then a supported locale in the language settings - * causes a strange error in which the order of the two locales get randomly flipped. - * This causes some resources to be loaded in the supported language and others in the - * default language. I don't know what causes that to happen, but this code removes - * unsupported locales from the configuration as a measure to prevent the strange behaviour. - **/ - - - newBase?.resources?.configuration?.let { configuration -> - val locales = configuration.locales - LocaleList(*getSupportedLocales(locales, supportedLanguages)) - .takeUnless { it.isEmpty } - ?.let { - configuration.setLocales(it) - } + * Static constructor for SupportedLanguageContextWrapper + * + * @param context base context to wrap + * @return wrapped context + */ + fun wrap(context: Context?): SupportedLanguageContextWrapper { + /** + * When selecting a non supported locale and then a supported locale in the language settings + * causes a strange error in which the order of the two locales get randomly flipped. + * This causes some resources to be loaded in the supported language and others in the + * default language. I don't know what causes that to happen, but this code removes + * unsupported locales from the configuration as a measure to prevent the strange behaviour. + **/ + + + context?.resources?.configuration?.let { configuration -> + val locales = configuration.locales + LocaleList(*getSupportedLocales(locales)) + .takeUnless { it.isEmpty } + ?.let { + configuration.setLocales(it) + } + } + return SupportedLanguageContextWrapper(context) } - super.attachBaseContext(newBase) + + private fun getSupportedLocales( + locales: LocaleList, + ) = (0 until locales.size()).mapNotNull { i -> + locales[i].takeIf { locale -> supportedLanguages.contains(locale.language) } + }.toTypedArray() } - private fun getSupportedLocales( - locales: LocaleList, - supportedLanguages: List, - ) = (0 until locales.size()).mapNotNull { i -> - locales[i].takeIf { locale -> supportedLanguages.contains(locale.language) } - }.toTypedArray() } \ No newline at end of file From 4a89348220e7ea5fc2f06f0d031a769ed560d7c1 Mon Sep 17 00:00:00 2001 From: Gregg Meyrick Jover Date: Tue, 11 Apr 2023 15:41:19 +0800 Subject: [PATCH 264/334] AND-15835: Fix Incoming and Outgoing Shares UI When Sorting in Grid View Mode When the user is in IncomingSharesFragment or OutgoingSharesFragment and selects a different Sort Order in Grid View mode, the UI temporarily switches to List View before going back to Grid View with the new Sort Order applied. This is due to the ManagerActivity functions refreshCloudOrder() and refreshOthersOrder(), which calls refreshSharesPageAdapter(). Calling this function creates new instances for IncomingSharesFragment, OutgoingSharesFragment and LinksFragment. As a result, both IncomingSharesFragment and OutgoingSharesFragment will begin with List View mode before switching to Grid View mode. Changelog: - Remove ManagerActivity calls to refreshCloudOrder() and refreshOthersOrder() in SortByBottomSheetDialogFragment. - Configure affected Fragments to observe the LiveData SortByHeaderViewModel.orderChangeEvent, so that they can refresh their own content whenever a Sort Order change happens. This also prevents having to create new instances for IncomingSharesFragment, OutgoingSharesFragment and LinksFragment. --- .../SortByBottomSheetDialogFragment.kt | 26 ++++++++++--------- .../clouddrive/FileBrowserFragment.kt | 15 ++++++----- .../app/presentation/inbox/InboxFragment.kt | 19 ++++++++------ .../rubbishbin/RubbishBinFragment.kt | 3 +++ .../app/presentation/search/SearchFragment.kt | 5 ++++ .../shares/incoming/IncomingSharesFragment.kt | 3 +++ .../shares/links/LinksFragment.kt | 3 +++ .../shares/outgoing/OutgoingSharesFragment.kt | 3 +++ 8 files changed, 50 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/SortByBottomSheetDialogFragment.kt b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/SortByBottomSheetDialogFragment.kt index 75d8d6ebbdd..f6583a267f8 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/SortByBottomSheetDialogFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/SortByBottomSheetDialogFragment.kt @@ -22,7 +22,6 @@ import mega.privacy.android.app.R import mega.privacy.android.app.databinding.BottomSheetSortByBinding import mega.privacy.android.app.fragments.homepage.SortByHeaderViewModel import mega.privacy.android.app.main.FileExplorerActivity -import mega.privacy.android.app.main.ManagerActivity import mega.privacy.android.app.utils.ColorUtils import mega.privacy.android.app.utils.Constants.BROADCAST_ACTION_INTENT_UPDATE_ORDER import mega.privacy.android.app.utils.Constants.EVENT_ORDER_CHANGE @@ -33,18 +32,23 @@ import mega.privacy.android.app.utils.Constants.ORDER_CLOUD import mega.privacy.android.app.utils.Constants.ORDER_FAVOURITES import mega.privacy.android.app.utils.Constants.ORDER_OFFLINE import mega.privacy.android.app.utils.Constants.ORDER_OTHERS -import mega.privacy.android.app.utils.callManager import mega.privacy.android.data.mapper.SortOrderIntMapper import mega.privacy.android.domain.entity.SortOrder import java.util.Locale import javax.inject.Inject +/** + * A [BaseBottomSheetDialogFragment] that displays a list of Sort Options + */ @AndroidEntryPoint class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { companion object { private const val ORDER_TYPE = "ORDER_TYPE" + /** + * Specify behavior when this Fragment is initialized + */ @JvmStatic fun newInstance(orderType: Int): SortByBottomSheetDialogFragment { val fragment = SortByBottomSheetDialogFragment() @@ -73,6 +77,9 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { lateinit var sortOrderIntMapper: SortOrderIntMapper + /** + * onCreateView() + */ override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -84,6 +91,9 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { return contentView } + /** + * onViewCreated() + */ @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val sortByName = getString(R.string.sortby_name) @@ -221,9 +231,7 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { sortByHeaderViewModel.offlineSortOrder.value, ) ) - if (requireActivity() is ManagerActivity) { - (requireActivity() as ManagerActivity).refreshCloudOrder() - } else if (requireActivity() is FileExplorerActivity) { + if (requireActivity() is FileExplorerActivity) { updateFileExplorerOrder(sortOrderIntMapper(order)) } } @@ -240,9 +248,7 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { sortByHeaderViewModel.offlineSortOrder.value, ) ) - if (requireActivity() is ManagerActivity) { - (requireActivity() as ManagerActivity).refreshOthersOrder() - } else if (requireActivity() is FileExplorerActivity) { + if (requireActivity() is FileExplorerActivity) { updateFileExplorerOrder(sortOrderIntMapper(order)) } } @@ -256,10 +262,6 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { order ) ) - - callManager { manager -> - manager.refreshOthersOrder() - } } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt index af80c349861..ac7a940bae0 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt @@ -29,6 +29,7 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.RecyclerView import com.jeremyliao.liveeventbus.LiveEventBus @@ -587,13 +588,13 @@ class FileBrowserFragment : RotatableFragment() { } } } - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, - EventObserver { showSortByPanel() } - ) - } - } + sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, + EventObserver { showSortByPanel() } + ) + sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver { + fileBrowserViewModel.refreshNodes() + hideMultipleSelect() + }) LiveEventBus.get(EVENT_SHOW_MEDIA_DISCOVERY, Unit::class.java) .observe(this) { showMediaDiscovery(true) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/inbox/InboxFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/inbox/InboxFragment.kt index 2df35dcc771..ac80bec8488 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/inbox/InboxFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/inbox/InboxFragment.kt @@ -142,13 +142,12 @@ class InboxFragment : RotatableFragment() { ): View? { Timber.d("onCreateView()") - viewLifecycleOwner.lifecycleScope.launch { - viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { - sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, - EventObserver { showSortByPanel() } - ) - } - } + sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, + EventObserver { showSortByPanel() } + ) + sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver { + viewModel.refreshInboxNodes() + }) val display = requireActivity().windowManager.defaultDisplay val outMetrics = DisplayMetrics() @@ -335,7 +334,11 @@ class InboxFragment : RotatableFragment() { when (item.itemId) { R.id.cab_menu_download -> { (requireActivity() as ManagerActivity).saveNodesToDevice( - selectedNodes, false, false, false, false + nodes = selectedNodes, + highPriority = false, + isFolderLink = false, + fromMediaViewer = false, + fromChat = false, ) clearSelections() hideMultipleSelect() diff --git a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt index 9e4c1da15d7..3e4443a6fef 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt @@ -134,6 +134,9 @@ class RubbishBinFragment : Fragment() { } sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, EventObserver { showSortByPanel() }) + sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver { + rubbishBinViewModel.refreshNodes() + }) viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt index c325351adb9..f252dfaedb0 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt @@ -192,6 +192,11 @@ class SearchFragment : RotatableFragment() { viewLifecycleOwner, EventObserver { managerActivity.showNewSortByPanel(Constants.ORDER_CLOUD) }) + sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver { + hideMultipleSelect() + refresh() + }) + searchViewModel.updateNodes.observe( viewLifecycleOwner, EventObserver { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt index 88dfedfa7db..ed302967478 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/incoming/IncomingSharesFragment.kt @@ -270,6 +270,9 @@ class IncomingSharesFragment : MegaNodeBaseFragment() { sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, EventObserver { showSortByPanel() }) + sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver { + viewModel.refreshIncomingSharesNode() + }) viewLifecycleOwner.collectFlow(sortByHeaderViewModel.state) { state -> updateViewType(state.viewType) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt index 19ccdbc0fec..fb94af46afb 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/links/LinksFragment.kt @@ -221,6 +221,9 @@ class LinksFragment : MegaNodeBaseFragment() { sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, EventObserver { showSortByPanel() }) + sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver { + viewModel.refreshLinksSharesNode() + }) } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt index ad9719c33a9..d81b90087ba 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/shares/outgoing/OutgoingSharesFragment.kt @@ -255,6 +255,9 @@ class OutgoingSharesFragment : MegaNodeBaseFragment() { sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, EventObserver { showSortByPanel() }) + sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver { + viewModel.refreshOutgoingSharesNode() + }) viewLifecycleOwner.collectFlow(sortByHeaderViewModel.state) { state -> updateViewType(state.viewType) From f73ca855587d4f67b05543fdb07dcc59a94c34af Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Mon, 17 Apr 2023 08:16:03 +0700 Subject: [PATCH 265/334] AND-16237: Fix Rubbish bin does show options menu --- .../presentation/rubbishbin/RubbishBinFragment.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt index 3e4443a6fef..cf1892d38d8 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinFragment.kt @@ -33,9 +33,12 @@ import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import mega.privacy.android.app.MimeTypeList import mega.privacy.android.app.R +import mega.privacy.android.app.arch.extensions.collectFlow import mega.privacy.android.app.components.CustomizedGridLayoutManager import mega.privacy.android.app.components.NewGridRecyclerView import mega.privacy.android.app.components.PositionDividerItemDecoration @@ -156,7 +159,6 @@ class RubbishBinFragment : Fragment() { } (requireActivity() as ManagerActivity).setToolbarTitle() - (requireActivity() as ManagerActivity).invalidateOptionsMenu() adapter = MegaNodeAdapter(requireActivity(), this@RubbishBinFragment, @@ -251,6 +253,14 @@ class RubbishBinFragment : Fragment() { DragToExitSupport.observeDragSupportEvents(viewLifecycleOwner, recyclerView, Constants.VIEWER_FROM_RUBBISH_BIN) + setupObserver() + } + + private fun setupObserver() { + viewLifecycleOwner.collectFlow(rubbishBinViewModel.state.map { it.nodes.isEmpty() } + .distinctUntilChanged()) { + requireActivity().invalidateOptionsMenu() + } } /** From 7a144d8a67a1c0f9f0a386411d13c10e0f41ddd7 Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Thu, 20 Apr 2023 10:47:07 +0700 Subject: [PATCH 266/334] AND-16161: Fix handleRootNodeAndHeartbeatState Intent.getAction NullPointerException (cherry picked from commit 623b7119d6adbbb6af3d422f4ad1d3aad91de6b6) --- .../main/java/mega/privacy/android/app/main/ManagerActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt index fe523058c36..2ce2269dad3 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt @@ -1979,7 +1979,7 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf intent = null } } - if (intent.action != null) { + if (intent?.action != null) { if (intent.action == Constants.ACTION_SHOW_TRANSFERS) { if (intent.getBooleanExtra(Constants.OPENED_FROM_CHAT, false)) { sendBroadcast(Intent(ACTION_CLOSE_CHAT_AFTER_OPEN_TRANSFERS)) From 28cd6afea9158bfee6f35ff132b9f3aedf726c82 Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Thu, 20 Apr 2023 16:58:31 +0800 Subject: [PATCH 267/334] CC-4225 AND - the subtitles empty icon is wrong in Dark mode MR link: https://code.developers.mega.co.nz/mobile/android/android/-/merge_requests/5163 --- .../app/mediaplayer/SelectSubtitleFileView.kt | 4 +-- .../res/drawable-night/ic_subtitles_empty.xml | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 app/src/main/res/drawable-night/ic_subtitles_empty.xml diff --git a/app/src/main/java/mega/privacy/android/app/mediaplayer/SelectSubtitleFileView.kt b/app/src/main/java/mega/privacy/android/app/mediaplayer/SelectSubtitleFileView.kt index 7d69cf6f938..6a38901bae4 100644 --- a/app/src/main/java/mega/privacy/android/app/mediaplayer/SelectSubtitleFileView.kt +++ b/app/src/main/java/mega/privacy/android/app/mediaplayer/SelectSubtitleFileView.kt @@ -173,8 +173,8 @@ internal fun SelectSubtitleFileView( Button( modifier = Modifier.padding(top = 8.dp, bottom = 8.dp, start = 24.dp), colors = ButtonDefaults.buttonColors( - backgroundColor = colorResource(id = R.color.teal_300), - disabledBackgroundColor = colorResource(id = R.color.teal_100) + backgroundColor = colorResource(id = R.color.teal_300_teal_200), + disabledBackgroundColor = colorResource(id = R.color.teal_200_alpha_038) ), onClick = { onAddSubtitleCallback(viewModel.getSelectedSubtitleFileInfoFlow().value) diff --git a/app/src/main/res/drawable-night/ic_subtitles_empty.xml b/app/src/main/res/drawable-night/ic_subtitles_empty.xml new file mode 100644 index 00000000000..6bdea20b0d2 --- /dev/null +++ b/app/src/main/res/drawable-night/ic_subtitles_empty.xml @@ -0,0 +1,33 @@ + + + + + + + From 2023bea2791fbd67cd8f8e1b70bd7890857eeb4d Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Thu, 20 Apr 2023 17:09:17 +0800 Subject: [PATCH 268/334] AP-153 The upgrade button is unavailable MR link: https://jira.developers.mega.co.nz/browse/AP-153 --- .../android/app/presentation/myaccount/MyAccountFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountFragment.kt index 1a876eca14f..0d755db323d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountFragment.kt @@ -333,7 +333,7 @@ class MyAccountFragment : Fragment(), Scrollable { state.accountType.toAccountAttributes().let { account -> binding.upgradeButton.apply { - isEnabled = false + isEnabled = !state.isBusinessAccount text = getString( when { state.isBusinessAccount && state.isMasterBusinessAccount -> R.string.admin_label From 6b9c01ffa36026e32683b69f3d9b460e00b42394 Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Fri, 21 Apr 2023 08:55:57 +0700 Subject: [PATCH 269/334] AND-16290: Fix crash did not then call Service.startForeground in DownloadService (cherry picked from commit 8c2461c53b9b7bafc5c0284b3b80a279ad03534e) --- .../privacy/android/app/DownloadService.kt | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/DownloadService.kt b/app/src/main/java/mega/privacy/android/app/DownloadService.kt index f60d5df2b37..cac28b7b5e9 100644 --- a/app/src/main/java/mega/privacy/android/app/DownloadService.kt +++ b/app/src/main/java/mega/privacy/android/app/DownloadService.kt @@ -283,23 +283,13 @@ internal class DownloadService : Service(), MegaRequestListenerInterface { } private fun startForeground() { - CoroutineScope(ioDispatcher).launch { - if (getDownloadCount() > 0) { - isForeground = kotlin.runCatching { - val notification = createInitialNotification() - startForeground( - Constants.NOTIFICATION_DOWNLOAD, - notification - ) - }.fold( - onSuccess = { true }, - onFailure = { - Timber.w(it) - false - } - ) - } - } + isForeground = runCatching { + val notification = createInitialNotification() + startForeground( + Constants.NOTIFICATION_DOWNLOAD, + notification + ) + }.isSuccess } @Suppress("DEPRECATION") @@ -493,6 +483,10 @@ internal class DownloadService : Service(), MegaRequestListenerInterface { } else { Timber.w("currentDir is not a directory") } + + if (getDownloadCount() <= 0) { + cancel() + } } private fun handlePublicNode(intent: Intent): Boolean { From dc17ba36d990c9d89066ddaa6c33fa8dd83da460 Mon Sep 17 00:00:00 2001 From: rohitsoni Date: Fri, 21 Apr 2023 10:06:48 +0530 Subject: [PATCH 270/334] Updated SDK hotfix --- build.gradle | 2 +- sdk/src/main/jni/mega/sdk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 87f74ca027f..2c9e5905818 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230419.061555-rel' + megaSdkVersion = '20230421.031525-rel' } ext.shouldSuppressWarnings = { -> diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index a5d24cfddc9..e606f74c07e 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit a5d24cfddc98ac89c40487db73e6e66db4751edf +Subproject commit e606f74c07ebd3a51dc79f99aa1c74d29f6ec32c From 7ae8b54bde24b4b3121a8dd414aa616088a5a966 Mon Sep 17 00:00:00 2001 From: Luong Hai Date: Mon, 24 Apr 2023 14:39:45 +1200 Subject: [PATCH 271/334] AND-16290/T10041256: Fix crash did not then call Service.startForeground in DownloadService (cherry picked from commit 5395cbdbea62d76136666386fa175e301f58930e) --- .../privacy/android/app/DownloadService.kt | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/DownloadService.kt b/app/src/main/java/mega/privacy/android/app/DownloadService.kt index cac28b7b5e9..3e251893917 100644 --- a/app/src/main/java/mega/privacy/android/app/DownloadService.kt +++ b/app/src/main/java/mega/privacy/android/app/DownloadService.kt @@ -389,23 +389,23 @@ internal class DownloadService : Service(), MegaRequestListenerInterface { // we don't need to create ioDispatcher here, in already run in Background Thread by rx java setup runBlocking { - processIntent(intent) + val isScheduleDownload = processIntent(intent) + if (!isScheduleDownload && getDownloadCount() <= 0) { + cancel() + } } } - private suspend fun processIntent( - intent: Intent, - ) { - - if (addPendingIntentIfNotLoggedIn(intent)) return - if (handlePublicNode(intent)) return + private suspend fun processIntent(intent: Intent): Boolean { + if (addPendingIntentIfNotLoggedIn(intent)) return false + if (handlePublicNode(intent)) return false val isFolderLink = intent.getBooleanExtra(EXTRA_FOLDER_LINK, false) val fromMV = intent.getBooleanExtra(EXTRA_FROM_MV, false) Timber.d("fromMV: %s", fromMV) val contentUri = intent.getStringExtra(EXTRA_CONTENT_URI)?.let { Uri.parse(it) } val highPriority = intent.getBooleanExtra(Constants.HIGH_PRIORITY_TRANSFER, false) - val node: MegaNode = getNodeForIntent(intent, isFolderLink) ?: return + val node: MegaNode = getNodeForIntent(intent, isFolderLink) ?: return false fromMediaViewers[node.handle] = fromMV currentDir = getDir(intent) @@ -428,7 +428,7 @@ internal class DownloadService : Service(), MegaRequestListenerInterface { if (megaApi.getNumPendingDownloadsNonBackground() == 0) { onQueueComplete(node.handle) } - return + return true } acquireLocks() if (contentUri != null || currentDir?.isDirectory == true) { @@ -480,13 +480,12 @@ internal class DownloadService : Service(), MegaRequestListenerInterface { highPriority, token ) + return true } else { Timber.w("currentDir is not a directory") } - if (getDownloadCount() <= 0) { - cancel() - } + return false } private fun handlePublicNode(intent: Intent): Boolean { From 777379b7415ad6e7859fe89805247e66af95f7f5 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Mon, 24 Apr 2023 19:19:52 +1200 Subject: [PATCH 272/334] AND-16303 Fix Account Type & Achievements Delay Loading --- .../presentation/myaccount/MyAccountFragment.kt | 6 +----- .../myaccount/MyAccountHomeViewModel.kt | 16 ++++++++++++---- .../myaccount/model/MyAccountHomeUIState.kt | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountFragment.kt index 0d755db323d..1e0a8cb3eaf 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountFragment.kt @@ -128,7 +128,6 @@ class MyAccountFragment : Fragment(), Scrollable { */ override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - viewModel.refreshAccountInfo() messageResultCallback = activity as? MessageResultCallback } @@ -256,7 +255,6 @@ class MyAccountFragment : Fragment(), Scrollable { */ override fun onDestroy() { super.onDestroy() - changeApiServerDialog?.dismiss() } @@ -382,9 +380,7 @@ class MyAccountFragment : Fragment(), Scrollable { private fun setAchievements(isAchievementsEnabled: Boolean) { binding.achievementsLayout.apply { - isVisible = isAchievementsEnabled - - if (!isVisible) { + if (!isVisible || isAchievementsEnabled.not()) { return@apply } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountHomeViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountHomeViewModel.kt index 5a32fe2ee59..ba4fb83feba 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountHomeViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountHomeViewModel.kt @@ -183,19 +183,27 @@ class MyAccountHomeViewModel @Inject constructor( viewModelScope.launch { runCatching { val accountDetails = getAccountDetailsUseCase(false) + _uiState.update { + it.copy( + accountType = accountDetails.accountTypeIdentifier, + isBusinessAccount = accountDetails.isBusinessAccount && accountDetails.accountTypeIdentifier == AccountType.BUSINESS, + isMasterBusinessAccount = accountDetails.isMasterBusinessAccount, + ) + } + val isBusinessStatusActive = getBusinessStatusUseCase().let { status -> (status == BusinessAccountStatus.GracePeriod || status == BusinessAccountStatus.Expired).not() } + _uiState.update { + it.copy(isBusinessStatusActive = isBusinessStatusActive) + } + val achievements = getAccountAchievements( AchievementType.MEGA_ACHIEVEMENT_ADD_PHONE, awardIndex = 0, ) _uiState.update { it.copy( - accountType = accountDetails.accountTypeIdentifier, - isBusinessAccount = accountDetails.isBusinessAccount && accountDetails.accountTypeIdentifier == AccountType.BUSINESS, - isMasterBusinessAccount = accountDetails.isMasterBusinessAccount, - isBusinessStatusActive = isBusinessStatusActive, isAchievementsEnabled = achievements != null, bonusStorageSms = achievements?.grantedStorage ?: 0 ) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/myaccount/model/MyAccountHomeUIState.kt b/app/src/main/java/mega/privacy/android/app/presentation/myaccount/model/MyAccountHomeUIState.kt index 32a4b51516a..8901fe4605d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/myaccount/model/MyAccountHomeUIState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/myaccount/model/MyAccountHomeUIState.kt @@ -41,7 +41,7 @@ data class MyAccountHomeUIState( val canVerifyPhoneNumber: Boolean = false, val avatar: File? = null, val avatarColor: Int? = null, - val accountType: AccountType? = AccountType.FREE, + val accountType: AccountType? = null, val isBusinessAccount: Boolean = false, val isMasterBusinessAccount: Boolean = false, val isAchievementsEnabled: Boolean = false, From f944665bd5408d6f99c0ff722df4e455cb7e0adc Mon Sep 17 00:00:00 2001 From: Atiqur Rahman Date: Tue, 25 Apr 2023 18:04:16 +1200 Subject: [PATCH 273/334] AP-168: Fix initial next button state to enabled --- .../app/presentation/verification/SMSVerificationViewModel.kt | 1 + .../presentation/verification/model/SMSVerificationUIState.kt | 2 +- .../smsVerification/SMSVerificationViewModelTest.kt | 4 ++-- .../app/presentation/verification/SMSVerificationViewTest.kt | 2 ++ 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/verification/SMSVerificationViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/verification/SMSVerificationViewModel.kt index 9fc73a125a6..2f351d6f778 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/verification/SMSVerificationViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/verification/SMSVerificationViewModel.kt @@ -238,6 +238,7 @@ class SMSVerificationViewModel @Inject constructor( _uiState.update { state -> state.copy( isVerificationCodeSent = true, + isNextEnabled = true ) } }.onFailure { error -> diff --git a/app/src/main/java/mega/privacy/android/app/presentation/verification/model/SMSVerificationUIState.kt b/app/src/main/java/mega/privacy/android/app/presentation/verification/model/SMSVerificationUIState.kt index fd51ea15f12..28dca892909 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/verification/model/SMSVerificationUIState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/verification/model/SMSVerificationUIState.kt @@ -81,7 +81,7 @@ data class SMSVerificationUIState( /** * whether sent button should be disabled or not */ - val isNextEnabled: Boolean = false, + val isNextEnabled: Boolean = true, ) { /** diff --git a/app/src/test/java/mega/privacy/android/app/presentation/smsVerification/SMSVerificationViewModelTest.kt b/app/src/test/java/mega/privacy/android/app/presentation/smsVerification/SMSVerificationViewModelTest.kt index 476f7749457..8d4aed0327f 100644 --- a/app/src/test/java/mega/privacy/android/app/presentation/smsVerification/SMSVerificationViewModelTest.kt +++ b/app/src/test/java/mega/privacy/android/app/presentation/smsVerification/SMSVerificationViewModelTest.kt @@ -150,12 +150,12 @@ class SMSVerificationViewModelTest { } @Test - fun `test that sms send code states are updated when verification code is send`() = runTest { + fun `test that sms send code states are updated when verification code is sent`() = runTest { val phoneNumber = "+012324567" val expected = getInitialState().copy( isVerificationCodeSent = true, - isNextEnabled = false, + isNextEnabled = true, phoneNumber = phoneNumber ) whenever(formatPhoneNumber(any(), any())).thenReturn(phoneNumber) diff --git a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/verification/SMSVerificationViewTest.kt b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/verification/SMSVerificationViewTest.kt index 8f6706f24e7..97c073ad1c3 100644 --- a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/verification/SMSVerificationViewTest.kt +++ b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/verification/SMSVerificationViewTest.kt @@ -1,6 +1,7 @@ package test.mega.privacy.android.app.presentation.verification import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsEnabled import androidx.compose.ui.test.assertIsNotEnabled import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag @@ -45,6 +46,7 @@ class SMSVerificationViewTest { onNodeWithText(state.countryCodeText).assertIsDisplayed() onNodeWithText(R.string.verify_account_phone_number_placeholder).assertIsDisplayed() onNodeWithTag(NEXT_BUTTON_TEST_TAG).assertIsDisplayed() + onNodeWithTag(NEXT_BUTTON_TEST_TAG).assertIsEnabled() } } From 95c89d6217aea8522d3d388d292d18c97e5d68b6 Mon Sep 17 00:00:00 2001 From: Nikhil Nagori Date: Wed, 26 Apr 2023 03:03:51 +1200 Subject: [PATCH 274/334] AND-16328 Fix ResourceNotFoundException --- .../app/presentation/folderlink/FolderLinkActivity.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/folderlink/FolderLinkActivity.kt b/app/src/main/java/mega/privacy/android/app/presentation/folderlink/FolderLinkActivity.kt index c612cd305f9..adeca20c72d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/folderlink/FolderLinkActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/folderlink/FolderLinkActivity.kt @@ -684,14 +684,14 @@ class FolderLinkActivity : TransfersManagementActivity(), MegaRequestListenerInt askForDecryptionKeyDialog() } else -> { - try { + if (it.errorDialogTitle != -1 && it.errorDialogContent != -1) { Timber.w("Show error dialog") showErrorDialog(it.errorDialogTitle, it.errorDialogContent) - - } catch (ex: Exception) { + } else if (it.snackBarMessage != -1) { showSnackbar(it.snackBarMessage) finish() } + } } } From 1ae6fdb3ec3da62b30e305194b77ebd0111b8c61 Mon Sep 17 00:00:00 2001 From: Luong Hai Date: Thu, 27 Apr 2023 11:34:35 +1200 Subject: [PATCH 275/334] FM-167: App shows "Preparing files" notifications in wrong situations (cherry picked from commit 9cf5907590d2583f59abd4ef95ef6af2d9ab7975) --- .../java/mega/privacy/android/app/DownloadService.kt | 7 ++++++- .../java/mega/privacy/android/app/UploadService.kt | 12 +++++++++++- .../transfers/TransfersManagementViewModel.kt | 8 +++++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/DownloadService.kt b/app/src/main/java/mega/privacy/android/app/DownloadService.kt index 3e251893917..c03ab95fd29 100644 --- a/app/src/main/java/mega/privacy/android/app/DownloadService.kt +++ b/app/src/main/java/mega/privacy/android/app/DownloadService.kt @@ -28,6 +28,7 @@ import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -191,6 +192,8 @@ internal class DownloadService : Service(), MegaRequestListenerInterface { } } + private var monitorPausedTransfersJob: Job? = null + @SuppressLint("NewApi") override fun onCreate() { super.onCreate() @@ -226,7 +229,7 @@ internal class DownloadService : Service(), MegaRequestListenerInterface { @SuppressLint("WrongConstant") private fun setReceivers() { - applicationScope.launch { + monitorPausedTransfersJob = applicationScope.launch { monitorPausedTransfers().collectLatest { // delay 1 second to refresh the pause notification to prevent update is missed Handler(Looper.getMainLooper()).postDelayed( @@ -333,6 +336,7 @@ internal class DownloadService : Service(), MegaRequestListenerInterface { stopForeground() LiveEventBus.get(EVENT_FINISH_SERVICE_IF_NO_TRANSFERS, Boolean::class.java) .removeObserver(stopServiceObserver) + monitorPausedTransfersJob?.cancel() super.onDestroy() } @@ -343,6 +347,7 @@ internal class DownloadService : Service(), MegaRequestListenerInterface { Timber.d("Cancel intent") canceled = true megaApi.cancelTransfers(MegaTransfer.TYPE_DOWNLOAD) + stopForeground() return START_NOT_STICKY } rxSubscriptions.add(Single.just(intent) diff --git a/app/src/main/java/mega/privacy/android/app/UploadService.kt b/app/src/main/java/mega/privacy/android/app/UploadService.kt index 7330848d9e6..1b90b5eb198 100644 --- a/app/src/main/java/mega/privacy/android/app/UploadService.kt +++ b/app/src/main/java/mega/privacy/android/app/UploadService.kt @@ -29,6 +29,7 @@ import io.reactivex.rxjava3.core.Completable import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import mega.privacy.android.app.MegaApplication.Companion.getInstance @@ -152,6 +153,8 @@ class UploadService : Service() { } } + private var monitorPausedTransfersJob: Job? = null + @SuppressLint("NewApi", "CheckResult", "WrongConstant") override fun onCreate() { super.onCreate() @@ -179,7 +182,7 @@ class UploadService : Service() { ) } - applicationScope.launch { + monitorPausedTransfersJob = applicationScope.launch { monitorPausedTransfers().collectLatest { // delay 1 second to refresh the pause notification to prevent update is missed Handler(Looper.getMainLooper()).postDelayed( @@ -203,6 +206,7 @@ class UploadService : Service() { .observeOn(AndroidSchedulers.mainThread()) .subscribe({}) { t: Throwable? -> Timber.e(t) } } + is GetGlobalTransferUseCase.Result.OnTransferUpdate -> { val transfer = event.transfer doOnTransferUpdate(transfer) @@ -210,6 +214,7 @@ class UploadService : Service() { .observeOn(AndroidSchedulers.mainThread()) .subscribe({}) { t: Throwable? -> Timber.e(t) } } + is GetGlobalTransferUseCase.Result.OnTransferFinish -> { val transfer = event.transfer val error = event.error @@ -218,6 +223,7 @@ class UploadService : Service() { .observeOn(AndroidSchedulers.mainThread()) .subscribe({}) { t: Throwable? -> Timber.e(t) } } + is GetGlobalTransferUseCase.Result.OnTransferTemporaryError -> { val transfer = event.transfer val error = event.error @@ -226,6 +232,7 @@ class UploadService : Service() { .observeOn(AndroidSchedulers.mainThread()) .subscribe({}) { t: Throwable? -> Timber.e(t) } } + else -> {} } } @@ -304,6 +311,7 @@ class UploadService : Service() { rxSubscriptions.clear() LiveEventBus.get(EVENT_FINISH_SERVICE_IF_NO_TRANSFERS, Boolean::class.java) .removeObserver(stopServiceObserver) + monitorPausedTransfersJob?.cancel() super.onDestroy() } @@ -567,10 +575,12 @@ class UploadService : Service() { Constants.OVERQUOTA_STORAGE_STATE -> action = Constants.ACTION_OVERQUOTA_STORAGE Constants.PRE_OVERQUOTA_STORAGE_STATE -> action = Constants.ACTION_PRE_OVERQUOTA_STORAGE + Constants.NOT_OVERQUOTA_STATE -> { action = Constants.ACTION_SHOW_TRANSFERS putExtra(ManagerActivity.TRANSFERS_TAB, TransfersTab.PENDING_TAB) } + else -> { action = Constants.ACTION_SHOW_TRANSFERS putExtra(ManagerActivity.TRANSFERS_TAB, TransfersTab.PENDING_TAB) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/transfers/TransfersManagementViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/transfers/TransfersManagementViewModel.kt index db3f3c9d02f..76cf7efdd2c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/transfers/TransfersManagementViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/transfers/TransfersManagementViewModel.kt @@ -78,7 +78,7 @@ class TransfersManagementViewModel @Inject constructor( getPendingDownloadAndUpload(transfersInfo) } } - checkTransfersState() + checkTransfersState(false) } /** @@ -143,11 +143,13 @@ class TransfersManagementViewModel @Inject constructor( /** * Checks if transfers are paused. */ - fun checkTransfersState() = viewModelScope.launch { + fun checkTransfersState(shouldBroadcast: Boolean = true) = viewModelScope.launch { areAllTransfersPaused().let { paused -> if (paused) { _state.update { it.copy(transfersInfo = it.transfersInfo.copy(status = TransfersStatus.Paused)) } - broadcastPausedTransfers() + if (shouldBroadcast) { + broadcastPausedTransfers() + } } else { checkTransfersInfo(TransferType.NONE, false) } From 84bf1391804ae56728eba5ab43ab269cdf7bb965 Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Wed, 26 Apr 2023 15:34:34 +0700 Subject: [PATCH 276/334] AND-16341 Fix Used Transfer Wrong Variable (cherry picked from commit d280b42ec1adf99f2b6d59e62456510d4483d717) --- .../android/app/presentation/myaccount/MyAccountFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountFragment.kt index 1e0a8cb3eaf..ba8b6e525b1 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/myaccount/MyAccountFragment.kt @@ -308,7 +308,7 @@ class MyAccountFragment : Fragment(), Scrollable { usageBinding.updateBusinessOrProFlexi( requireContext(), formatSize(state.usedStorage), - formatSize(100000) + formatSize(state.usedTransfer) ) } else { usageBinding.update( From 7297d208c49a2aa244cb05deedf2ef5b9a2c3f10 Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Thu, 4 May 2023 09:52:56 +0700 Subject: [PATCH 277/334] AND-16358: Fix crash ExportRecoveryKeyActivity cannot be cast ManagerActivity (cherry picked from commit 3d03922d172ac392ec6344c82ef5e98859387f7c) --- .../privacy/android/app/main/controllers/AccountController.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/controllers/AccountController.kt b/app/src/main/java/mega/privacy/android/app/main/controllers/AccountController.kt index 3c5e0bfd16d..c85352da2ed 100644 --- a/app/src/main/java/mega/privacy/android/app/main/controllers/AccountController.kt +++ b/app/src/main/java/mega/privacy/android/app/main/controllers/AccountController.kt @@ -277,7 +277,7 @@ class AccountController @Inject constructor( return rKBitmap } - showAlert(context as ManagerActivity, context.getString(R.string.general_text_error), null) + showAlert(context, context.getString(R.string.general_text_error), null) return null } From 0288866dc909f4211efbb334d440342eeee09c16 Mon Sep 17 00:00:00 2001 From: Pau Dominkovics Coll Date: Fri, 5 May 2023 20:55:48 +1200 Subject: [PATCH 278/334] TRAN-112: reset invalid transfers when: 1-Transfers widget is touched, 2-Completed transfers section is shown (Transfers screen from menu drawer and select "Completed" tab), 3-A Transfers finish is received while the user is in completed transfers section --- .../managerFragments/TransfersBaseFragment.kt | 16 ++++++++++ .../android/app/main/ManagerActivity.kt | 7 ++++- .../managerSections/TransfersViewModel.kt | 29 +++++++++++++++++++ .../transfers/TransfersManagementActivity.kt | 1 + 4 files changed, 52 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/managerFragments/TransfersBaseFragment.kt b/app/src/main/java/mega/privacy/android/app/fragments/managerFragments/TransfersBaseFragment.kt index 04aee91f8b3..42207e19b0a 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/managerFragments/TransfersBaseFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/managerFragments/TransfersBaseFragment.kt @@ -88,6 +88,22 @@ open class TransfersBaseFragment : RotatableFragment() { return binding.root } + /** + * override onPause to inform viewModel + */ + override fun onPause() { + super.onPause() + viewModel.clearSelectedTab() + } + + /** + * override onResume to inform viewModel + */ + override fun onResume() { + super.onResume() + viewModel.resetSelectedTab() + } + private fun setupFlow() { viewModel.activeState.flowWithLifecycle( lifecycle = viewLifecycleOwner.lifecycle, diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt index 2806700aac4..0edb088ac54 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt @@ -1428,6 +1428,7 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf transfersFragment?.checkSelectModeAfterChangeTabOrDrawerItem() } } + transfersViewModel.setCurrentSelectedTab(selectedTab) } override fun onPageScrollStateChanged(state: Int) {} @@ -4161,6 +4162,11 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf MegaApplication.setRecentChatVisible(false) Util.resetActionBar(supportActionBar) updateTransfersWidget() + if (drawerItem == DrawerItem.TRANSFERS) { + transfersViewModel.resetSelectedTab() + } else { + transfersViewModel.clearSelectedTab() + } setCallWidget() if (item !== DrawerItem.CHAT) { //remove recent chat fragment as its life cycle get triggered unexpectedly, e.g. rotate device while not on recent chat page @@ -4337,7 +4343,6 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf showFabButton() } DrawerItem.TRANSFERS -> { - transfersManagement.setAreFailedTransfers(false) showHideBottomNavigationView(true) supportActionBar?.subtitle = null selectDrawerItemTransfers() diff --git a/app/src/main/java/mega/privacy/android/app/main/managerSections/TransfersViewModel.kt b/app/src/main/java/mega/privacy/android/app/main/managerSections/TransfersViewModel.kt index 3a577e78a00..cc5e677c00e 100644 --- a/app/src/main/java/mega/privacy/android/app/main/managerSections/TransfersViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/main/managerSections/TransfersViewModel.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.launch import mega.privacy.android.app.AndroidCompletedTransfer import mega.privacy.android.app.LegacyDatabaseHandler import mega.privacy.android.app.globalmanagement.TransfersManagement +import mega.privacy.android.app.presentation.manager.model.TransfersTab import mega.privacy.android.app.presentation.transfers.model.mapper.AndroidCompletedTransferMapper import mega.privacy.android.app.utils.Constants.INVALID_POSITION import mega.privacy.android.app.utils.TextUtil @@ -92,6 +93,8 @@ class TransfersViewModel @Inject constructor( private var completedTransfers = mutableListOf() private var transferCallback = 0L + private var currentTab = TransfersTab.NONE + private var previousTab = TransfersTab.NONE init { getAllActiveTransfers() @@ -204,6 +207,29 @@ class TransfersViewModel @Inject constructor( _activeTransfers.update { current } } + /** + * Set the current active transfers tab + */ + fun setCurrentSelectedTab(tab: TransfersTab) = viewModelScope.launch { + if (tab == TransfersTab.NONE && currentTab != TransfersTab.NONE) { + previousTab = currentTab + } + currentTab = tab + if (currentTab == TransfersTab.COMPLETED_TAB) { + transfersManagement.setAreFailedTransfers(false) + } + } + + /** + * clears the current selected tab, because the full section is not visible for instance + */ + fun clearSelectedTab() = setCurrentSelectedTab(TransfersTab.NONE) + + /** + * resets the selected tab when the section is visible again + */ + fun resetSelectedTab() = setCurrentSelectedTab(previousTab) + /** * Updates the UI in consequence after a transfer movement. * The update depends on if the movement finished with or without success. @@ -307,6 +333,9 @@ class TransfersViewModel @Inject constructor( _completedState.update { CompletedTransfersState.TransferFinishUpdated(completedTransfers.toList()) } + if (currentTab == TransfersTab.COMPLETED_TAB) { + transfersManagement.setAreFailedTransfers(false) + } } /** diff --git a/app/src/main/java/mega/privacy/android/app/presentation/transfers/TransfersManagementActivity.kt b/app/src/main/java/mega/privacy/android/app/presentation/transfers/TransfersManagementActivity.kt index bfce7dfc46d..700fb40fd74 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/transfers/TransfersManagementActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/transfers/TransfersManagementActivity.kt @@ -252,6 +252,7 @@ open class TransfersManagementActivity : PasscodeActivity() { } private fun onWidgetClick() { + transfersManagement.setAreFailedTransfers(false) if (this is ManagerActivity) { drawerItem = DrawerItem.TRANSFERS selectDrawerItem(this.drawerItem) From e2ef262339e21784fc50da33cd59b8c3064ffc38 Mon Sep 17 00:00:00 2001 From: Bhavna Thacker Date: Fri, 5 May 2023 16:07:00 +0530 Subject: [PATCH 279/334] T10155807: Fix for Previewable RAW files --- .../app/imageviewer/ImageViewerPageFragment.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/imageviewer/ImageViewerPageFragment.kt b/app/src/main/java/mega/privacy/android/app/imageviewer/ImageViewerPageFragment.kt index 455b11baa25..8aee1ac0975 100644 --- a/app/src/main/java/mega/privacy/android/app/imageviewer/ImageViewerPageFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/imageviewer/ImageViewerPageFragment.kt @@ -256,9 +256,7 @@ class ImageViewerPageFragment : Fragment() { .setControllerListener(controllerListener) .setAutoPlayAnimations(true) .build() - val zoomableController = - binding.image.zoomableController as AbstractAnimatedZoomableController - zoomableController.transform = transform + attachTransform() if (imageResult.isVideo) { binding.image.post { showVideoButton() } @@ -266,6 +264,13 @@ class ImageViewerPageFragment : Fragment() { } } + //Attach ZoomableDraweeView's transform to latest value in order to Retain Current Offset and Zoom + private fun attachTransform() { + val zoomableController = + binding.image.zoomableController as AbstractAnimatedZoomableController + zoomableController.transform = transform + } + private fun buildImageControllerListener() = object : BaseControllerListener() { override fun onFinalImageSet( id: String?, @@ -290,6 +295,7 @@ class ImageViewerPageFragment : Fragment() { binding.image.controller = Fresco.newDraweeControllerBuilder() .setImageRequest(imageResult.previewUri?.toUri()?.toImageRequest(false)) .build() + attachTransform() if (imageResult.isFullyLoaded) { binding.image.post { From 3e1d495c7cbfc5a75a6d9a0f7a8b1a56dda9072b Mon Sep 17 00:00:00 2001 From: rohitsoni Date: Fri, 5 May 2023 10:30:30 +0530 Subject: [PATCH 280/334] AND-16318 Fixed Local feature flag returning always true issue --- .../android/app/main/ManagerActivity.kt | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt index 0edb088ac54..214292e08e8 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt @@ -347,6 +347,7 @@ import mega.privacy.android.domain.entity.transfer.TransferState import mega.privacy.android.domain.qualifier.ApplicationScope import mega.privacy.android.domain.usecase.IsRequestPhoneNumberShownUseCase import mega.privacy.android.domain.usecase.SetRequestPhoneNumberShownUseCase +import mega.privacy.android.domain.usecase.featureflag.GetFeatureFlagValueUseCase import mega.privacy.android.feature.sync.ui.SyncFragment import nz.mega.sdk.MegaAccountDetails import nz.mega.sdk.MegaAchievementsDetails @@ -471,6 +472,9 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf @Inject lateinit var moveRequestMessageMapper: MoveRequestMessageMapper + @Inject + lateinit var getFeatureFlagValueUseCase: GetFeatureFlagValueUseCase + private val subscriptions = CompositeDisposable() //GET PRO ACCOUNT PANEL @@ -2534,16 +2538,14 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf /** * Checks if RubbishBinCompose enabled from [AppFeatures.RubbishBinCompose] */ - private fun isRubbishBinComposeEnabled(): Boolean { - return isFeatureEnabled(AppFeatures.RubbishBinCompose) - } + private fun isRubbishBinComposeEnabled(): Boolean = + isFeatureEnabled(AppFeatures.RubbishBinCompose) /** * Checks if ileBrowserCompose enabled from [AppFeatures.FileBrowserCompose] */ - private fun isFileBrowserComposeEnabled(): Boolean { - return isFeatureEnabled(AppFeatures.FileBrowserCompose) - } + private fun isFileBrowserComposeEnabled(): Boolean = + fileBrowserComposeFragment != null /** * Checks if some business warning has to be shown due to the status of the account. @@ -3364,18 +3366,20 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf tabLayoutTransfers.visibility = View.GONE viewPagerTransfers.visibility = View.GONE fragmentContainer.visibility = View.VISIBLE - if (isFileBrowserComposeEnabled()) { - fileBrowserComposeFragment = - (supportFragmentManager.findFragmentByTag(FragmentTag.CLOUD_DRIVE_COMPOSE.tag) as? FileBrowserComposeFragment - ?: FileBrowserComposeFragment.newInstance()).also { - replaceFragment(it, FragmentTag.CLOUD_DRIVE_COMPOSE.tag) - } - } else { - fileBrowserFragment = + + lifecycleScope.launch { + if (getFeatureFlagValueUseCase(AppFeatures.FileBrowserCompose)) { + fileBrowserComposeFragment = + (supportFragmentManager.findFragmentByTag(FragmentTag.CLOUD_DRIVE_COMPOSE.tag) as? FileBrowserComposeFragment + ?: FileBrowserComposeFragment.newInstance()).also { + replaceFragment(it, FragmentTag.CLOUD_DRIVE_COMPOSE.tag) + } + } else { (supportFragmentManager.findFragmentByTag(FragmentTag.CLOUD_DRIVE.tag) as? FileBrowserFragment ?: FileBrowserFragment.newInstance()).also { replaceFragment(it, FragmentTag.CLOUD_DRIVE.tag) } + } } } @@ -9682,7 +9686,8 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf fun showKeyboardForSearch() { if (searchView != null) { - searchView?.findViewById(androidx.appcompat.R.id.search_src_text)?.let { showKeyboardDelayed(it) } + searchView?.findViewById(androidx.appcompat.R.id.search_src_text) + ?.let { showKeyboardDelayed(it) } searchView?.requestFocus() } } From 89092b44445cd2fd62013c67a88c49b82a4d2650 Mon Sep 17 00:00:00 2001 From: Gregg Meyrick Jover Date: Mon, 8 May 2023 20:35:37 +0800 Subject: [PATCH 281/334] BAC-205: Fix "Restore" Option Appearing for Non-Deleted Backup Nodes When NodeOptionsBottomSheetDialogFragment is started with INBOX_MODE, set the visibility of optionRestoreFromRubbish to View.GONE in order to hide the "Restore" option. --- .../bottomsheet/NodeOptionsBottomSheetDialogFragment.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/bottomsheet/NodeOptionsBottomSheetDialogFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/bottomsheet/NodeOptionsBottomSheetDialogFragment.kt index 30dd3a9342e..cdf8086ee11 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/bottomsheet/NodeOptionsBottomSheetDialogFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/bottomsheet/NodeOptionsBottomSheetDialogFragment.kt @@ -393,7 +393,11 @@ class NodeOptionsBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { counterModify-- optionRestoreFromRubbish.visibility = View.GONE } - INBOX_MODE -> Timber.d("show My Backups bottom sheet") + INBOX_MODE -> { + Timber.d("show My Backups bottom sheet") + counterModify-- + optionRestoreFromRubbish.visibility = View.GONE + } RUBBISH_BIN_MODE -> { Timber.d("show Rubbish bottom sheet") optionEdit.visibility = View.GONE From 23dbf28dfde2b06450cf8486048ec56af0fe08fc Mon Sep 17 00:00:00 2001 From: Gregg Meyrick Jover Date: Tue, 9 May 2023 16:48:11 +0800 Subject: [PATCH 282/334] BAC-205: Fix "Restore" Behavior Not Working for Deleted Backup Nodes Changelog: - Create enum class RestoreType. This class specifies what "Restore" behavior should be executed. - Add State parameter restoreType in RubbishBinState. - Update onRestoreClicked() to check the selected Nodes if they came from Backups or not. The restoreType is set to RestoreType.MOVE if any of the selected Nodes came from Backups. Otherwise, RestoreType.RESTORE is set. - Create function onRestoreHandled() to reset restoreType back to null, once the View has acknowledged the "Restore" functionality. - Add Tests in RubbishBinViewModelTest. --- .../rubbishbin/RubbishBinComposeFragment.kt | 56 ++++++-- .../rubbishbin/RubbishBinViewModel.kt | 68 +++++++--- .../rubbishbin/model/RestoreType.kt | 19 +++ .../rubbishbin/model/RubbishBinState.kt | 4 +- .../rubbishbin/RubbishBinViewModelTest.kt | 121 +++++++++++++++++- 5 files changed, 233 insertions(+), 35 deletions(-) create mode 100644 app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/model/RestoreType.kt diff --git a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinComposeFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinComposeFragment.kt index 5acdb607b89..849f5071210 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinComposeFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinComposeFragment.kt @@ -27,10 +27,12 @@ import mega.privacy.android.app.arch.extensions.collectFlow import mega.privacy.android.app.fragments.homepage.EventObserver import mega.privacy.android.app.fragments.homepage.SortByHeaderViewModel import mega.privacy.android.app.main.ManagerActivity +import mega.privacy.android.app.main.controllers.NodeController import mega.privacy.android.app.presentation.data.NodeUIItem import mega.privacy.android.app.presentation.extensions.isDarkMode import mega.privacy.android.app.presentation.favourites.facade.StringUtilWrapper import mega.privacy.android.app.presentation.manager.ManagerViewModel +import mega.privacy.android.app.presentation.rubbishbin.model.RestoreType import mega.privacy.android.app.presentation.rubbishbin.view.RubbishBinComposeView import mega.privacy.android.app.utils.Constants import mega.privacy.android.app.utils.MegaApiUtils @@ -74,6 +76,9 @@ class RubbishBinComposeFragment : Fragment() { private var actionMode: ActionMode? = null + /** + * onCreateView + */ override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -111,12 +116,45 @@ class RubbishBinComposeFragment : Fragment() { fileCount = uiState.selectedFileNodes, folderCount = uiState.selectedFolderNodes ) - restoreMegaNode(uiState.selectedMegaNodes) + handleRestoreBehavior( + restoreType = uiState.restoreType, + selectedNodes = uiState.selectedMegaNodes, + selectedNodeHandles = uiState.selectedNodeHandles, + ) itemClickedEvenReceived(uiState.currFileNode) } } } + /** + * Handles the "Restore" functionality + * + * @param restoreType The behavior when the "Restore" button is clicked + * @param selectedNodes The list of Nodes selected by the User + * @param selectedNodeHandles The list of Node Handles selected by the User + */ + private fun handleRestoreBehavior( + restoreType: RestoreType?, + selectedNodes: List?, + selectedNodeHandles: List, + ) { + restoreType?.let { it -> + when (it) { + RestoreType.MOVE -> { + NodeController(requireActivity()).chooseLocationToMoveNodes(selectedNodeHandles) + } + RestoreType.RESTORE -> { + selectedNodes?.let { + ((requireActivity()) as ManagerActivity).restoreFromRubbish(it) + } + } + } + actionMode?.finish() + // Notify the ViewModel that the "Restore" behavior has been handled + viewModel.onRestoreHandled() + } + } + private fun getEmptyFolderDrawable(isRubbishBinEmpty: Boolean): Pair { return if (isRubbishBinEmpty) { Pair( @@ -137,6 +175,9 @@ class RubbishBinComposeFragment : Fragment() { } } + /** + * onViewCreated + */ override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupObserver() @@ -188,17 +229,6 @@ class RubbishBinComposeFragment : Fragment() { } } - /** - * Restore mega node from RubbishBin - */ - private fun restoreMegaNode(selectedMegaNodes: List?) { - selectedMegaNodes?.let { - ((requireActivity()) as ManagerActivity).restoreFromRubbish(it) - viewModel.clearAllSelectedNodes() - actionMode?.finish() - } - } - private inner class ActionBarCallback : ActionMode.Callback { override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean { val inflater = mode?.menuInflater @@ -322,7 +352,7 @@ class RubbishBinComposeFragment : Fragment() { .distinctUntilChanged()) { requireActivity().invalidateOptionsMenu() } - sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver{ + sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver { viewModel.refreshNodes() }) } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinViewModel.kt index cc779a84505..fbb76a16fce 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinViewModel.kt @@ -4,6 +4,9 @@ import android.app.Activity import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update @@ -15,15 +18,18 @@ import mega.privacy.android.app.domain.usecase.MonitorNodeUpdates import mega.privacy.android.app.extensions.updateItemAt import mega.privacy.android.app.presentation.data.NodeUIItem import mega.privacy.android.app.presentation.mapper.GetIntentToOpenFileMapper +import mega.privacy.android.app.presentation.rubbishbin.model.RestoreType import mega.privacy.android.app.presentation.rubbishbin.model.RubbishBinState import mega.privacy.android.app.utils.Constants import mega.privacy.android.domain.entity.node.FileNode import mega.privacy.android.domain.entity.node.FolderNode import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeChanges +import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.preference.ViewType import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle +import mega.privacy.android.domain.usecase.node.IsNodeDeletedFromBackupsUseCase import mega.privacy.android.domain.usecase.viewtype.MonitorViewType import mega.privacy.android.domain.usecase.viewtype.SetViewType import nz.mega.sdk.MegaApiJava.INVALID_HANDLE @@ -38,6 +44,7 @@ import javax.inject.Inject * @param monitorNodeUpdates Monitor node updates * @param getRubbishBinParentNodeHandle [GetParentNodeHandle] Fetch parent handle * @param getRubbishBinChildren [GetRubbishBinChildren] Fetch Rubbish Bin [Node] + * @param isNodeDeletedFromBackupsUseCase Checks whether the deleted Node came from Backups or not * @param setViewType [SetViewType] to set view type * @param monitorViewType [MonitorViewType] check view type * @param getIntentToOpenFileMapper [GetIntentToOpenFileMapper] @@ -49,6 +56,7 @@ class RubbishBinViewModel @Inject constructor( private val monitorNodeUpdates: MonitorNodeUpdates, private val getRubbishBinParentNodeHandle: GetParentNodeHandle, private val getRubbishBinChildren: GetRubbishBinChildren, + private val isNodeDeletedFromBackupsUseCase: IsNodeDeletedFromBackupsUseCase, private val setViewType: SetViewType, private val monitorViewType: MonitorViewType, private val getCloudSortOrder: GetCloudSortOrder, @@ -350,19 +358,15 @@ class RubbishBinViewModel @Inject constructor( /** * Clear the selections of items from NodesUiList and reset count of other Nodes */ - fun clearAllSelectedNodes() { - viewModelScope.launch { - _state.update { - it.copy( - nodeList = clearNodeUiItemList(), - selectedFileNodes = 0, - selectedFolderNodes = 0, - isInSelection = false, - selectedNodeHandles = emptyList(), - selectedMegaNodes = null - ) - } - } + fun clearAllSelectedNodes() = _state.update { + it.copy( + nodeList = clearNodeUiItemList(), + selectedFileNodes = 0, + selectedFolderNodes = 0, + isInSelection = false, + selectedNodeHandles = emptyList(), + selectedMegaNodes = null, + ) } /** @@ -375,9 +379,9 @@ class RubbishBinViewModel @Inject constructor( } /** - * This method will get List of [MegaNode] from handle to restore data + * Given a list of Node Handles, this retrieves the list of Nodes that were selected by the User */ - fun onRestoreClicked() { + fun retrieveSelectedMegaNodes() { val megaNodeList = mutableListOf() _state.value.selectedNodeHandles.forEach { val selectedMegaNode = state.value.nodes.find { megaNode -> megaNode.handle == it } @@ -385,22 +389,46 @@ class RubbishBinViewModel @Inject constructor( megaNodeList.add(megaNode) } } - updateSelectedMegaNode(megaNodeList) + updateSelectedMegaNodes(megaNodeList) } /** - * Update selected mega node to state + * Updates the selected Nodes to [RubbishBinState.selectedMegaNodes] */ - private fun updateSelectedMegaNode(selectedMegaNode: List?) { - viewModelScope.launch { + private fun updateSelectedMegaNodes(selectedMegaNodes: List?) = _state.update { + it.copy(selectedMegaNodes = selectedMegaNodes) + } + + /** + * Restores the list of selected Nodes when the "Restore" button is clicked + * + * If any of the Nodes is a Backup Node, the "Move" command is executed and will prompt the user + * to select a destination to restore the list of Nodes + * + * Otherwise, the list of Nodes will be restored back to where they came from + */ + fun onRestoreClicked() = viewModelScope.launch { + retrieveSelectedMegaNodes() + val selectedNodes = _state.value.selectedMegaNodes ?: emptyList() + if (selectedNodes.isNotEmpty()) { + val deferredResults = mutableListOf>() + for (node in selectedNodes) { + deferredResults += async { isNodeDeletedFromBackupsUseCase(NodeId(node.handle)) } + } + val hasBackupNodes = deferredResults.awaitAll().contains(true) _state.update { it.copy( - selectedMegaNodes = selectedMegaNode + restoreType = if (hasBackupNodes) RestoreType.MOVE else RestoreType.RESTORE, ) } } } + /** + * Acknowledges that the "Restore" behavior has been handled + */ + fun onRestoreHandled() = _state.update { it.copy(restoreType = null) } + private fun setPendingRefreshNodes() { _state.update { it.copy(isPendingRefresh = true) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/model/RestoreType.kt b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/model/RestoreType.kt new file mode 100644 index 00000000000..b14dcfb75da --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/model/RestoreType.kt @@ -0,0 +1,19 @@ +package mega.privacy.android.app.presentation.rubbishbin.model + +/** + * Enum class that specifies different behaviors for the "Restore" functionality in + * Rubbish Bin + */ +enum class RestoreType { + + /** + * Indicates that the selected Nodes should be restored back to where they came from + */ + RESTORE, + + /** + * Indicates that the User must select a destination to restore all selected Nodes. This is + * used when any of the selected Nodes is a Backup Node + */ + MOVE, +} \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/model/RubbishBinState.kt b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/model/RubbishBinState.kt index 8742fff2d88..5044a516c7b 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/model/RubbishBinState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/rubbishbin/model/RubbishBinState.kt @@ -22,6 +22,7 @@ import nz.mega.sdk.MegaNode * @property selectedNodeHandles List of selected node handles * @property selectedMegaNodes List of selected [MegaNode] * @property isRubbishBinEmpty If parent rubbish is empty or not + * @property restoreType Determines the specific "Restore" behavior */ data class RubbishBinState( val rubbishBinHandle: Long = -1L, @@ -38,5 +39,6 @@ data class RubbishBinState( val selectedNodeHandles: List = emptyList(), val selectedMegaNodes: List? = null, val isPendingRefresh: Boolean = false, - val isRubbishBinEmpty: Boolean = false + val isRubbishBinEmpty: Boolean = false, + val restoreType: RestoreType? = null, ) diff --git a/app/src/test/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinViewModelTest.kt b/app/src/test/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinViewModelTest.kt index f0b9473e871..4bf48748108 100644 --- a/app/src/test/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinViewModelTest.kt +++ b/app/src/test/java/mega/privacy/android/app/presentation/rubbishbin/RubbishBinViewModelTest.kt @@ -16,15 +16,20 @@ import mega.privacy.android.app.domain.usecase.GetRubbishBinChildrenNode import mega.privacy.android.app.domain.usecase.GetRubbishBinFolder import mega.privacy.android.app.presentation.data.NodeUIItem import mega.privacy.android.app.presentation.mapper.GetIntentToOpenFileMapper +import mega.privacy.android.app.presentation.rubbishbin.model.RestoreType import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.node.FileNode import mega.privacy.android.domain.entity.node.FolderNode import mega.privacy.android.domain.entity.node.Node import mega.privacy.android.domain.entity.node.NodeChanges +import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.NodeUpdate +import mega.privacy.android.domain.entity.node.TypedFileNode +import mega.privacy.android.domain.entity.node.TypedFolderNode import mega.privacy.android.domain.entity.preference.ViewType import mega.privacy.android.domain.usecase.GetCloudSortOrder import mega.privacy.android.domain.usecase.GetParentNodeHandle +import mega.privacy.android.domain.usecase.node.IsNodeDeletedFromBackupsUseCase import mega.privacy.android.domain.usecase.viewtype.MonitorViewType import mega.privacy.android.domain.usecase.viewtype.SetViewType import nz.mega.sdk.MegaNode @@ -32,6 +37,7 @@ import org.junit.After import org.junit.Before import org.junit.Rule import org.junit.Test +import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.times import org.mockito.kotlin.verify @@ -47,6 +53,7 @@ class RubbishBinViewModelTest { private val monitorNodeUpdates = FakeMonitorUpdates() private val getRubbishBinParentNodeHandle = mock() private val getRubbishBinChildren = mock() + private val isNodeDeletedFromBackupsUseCase = mock() private val setViewType = mock() private val monitorViewType = mock() private val getCloudSortOrder = mock() @@ -68,6 +75,7 @@ class RubbishBinViewModelTest { monitorNodeUpdates = monitorNodeUpdates, getRubbishBinParentNodeHandle = getRubbishBinParentNodeHandle, getRubbishBinChildren = getRubbishBinChildren, + isNodeDeletedFromBackupsUseCase = isNodeDeletedFromBackupsUseCase, setViewType = setViewType, monitorViewType = monitorViewType, getCloudSortOrder = getCloudSortOrder, @@ -82,6 +90,20 @@ class RubbishBinViewModelTest { val initial = awaitItem() Truth.assertThat(initial.rubbishBinHandle).isEqualTo(-1L) Truth.assertThat(initial.nodes).isEmpty() + Truth.assertThat(initial.parentHandle).isNull() + Truth.assertThat(initial.nodeList).isEmpty() + Truth.assertThat(initial.selectedFileNodes).isEqualTo(0) + Truth.assertThat(initial.selectedFolderNodes).isEqualTo(0) + Truth.assertThat(initial.isInSelection).isFalse() + Truth.assertThat(initial.currFileNode).isNull() + Truth.assertThat(initial.itemIndex).isEqualTo(-1) + Truth.assertThat(initial.currentViewType).isEqualTo(ViewType.LIST) + Truth.assertThat(initial.sortOrder).isEqualTo(SortOrder.ORDER_NONE) + Truth.assertThat(initial.selectedNodeHandles).isEmpty() + Truth.assertThat(initial.selectedMegaNodes).isNull() + Truth.assertThat(initial.isPendingRefresh).isFalse() + Truth.assertThat(initial.isRubbishBinEmpty).isFalse() + Truth.assertThat(initial.restoreType).isNull() } } @@ -373,7 +395,7 @@ class RubbishBinViewModelTest { underTest.refreshNodes() underTest.selectAllNodes() - underTest.onRestoreClicked() + underTest.retrieveSelectedMegaNodes() Truth.assertThat(underTest.state.value.selectedNodeHandles.size) .isEqualTo(underTest.state.value.selectedMegaNodes?.size) } @@ -409,6 +431,103 @@ class RubbishBinViewModelTest { Truth.assertThat(underTest.state.value.itemIndex).isNotEqualTo(-1) } + @Test + fun `test that restoring nodes will execute the move functionality when backup nodes are selected`() = + runTest { + val nodesListItem1 = mock() + val nodesListItem2 = mock() + val megaNode1 = mock() + val megaNode2 = mock() + whenever(nodesListItem1.id.longValue).thenReturn(1L) + whenever(nodesListItem2.id.longValue).thenReturn(2L) + whenever(megaNode1.handle).thenReturn(1L) + whenever(megaNode2.handle).thenReturn(2L) + whenever(getRubbishBinChildrenNode(underTest.state.value.rubbishBinHandle)).thenReturn( + listOf(megaNode1, megaNode2) + ) + whenever(getRubbishBinChildren(underTest.state.value.rubbishBinHandle)).thenReturn( + listOf(nodesListItem1, nodesListItem2) + ) + whenever(getCloudSortOrder()).thenReturn(SortOrder.ORDER_NONE) + whenever(isNodeDeletedFromBackupsUseCase(NodeId(any()))).thenReturn(true) + + underTest.refreshNodes() + underTest.selectAllNodes() + underTest.onRestoreClicked() + + underTest.state.test { + Truth.assertThat(awaitItem().restoreType).isEqualTo(RestoreType.MOVE) + } + } + + @Test + fun `test that restoring nodes will execute the move functionality when backup and non backup nodes are selected`() = + runTest { + val nodesListItem1 = mock() + val nodesListItem2 = mock() + val megaNode1 = mock() + val megaNode2 = mock() + whenever(nodesListItem1.id.longValue).thenReturn(1L) + whenever(nodesListItem2.id.longValue).thenReturn(2L) + whenever(megaNode1.handle).thenReturn(1L) + whenever(megaNode2.handle).thenReturn(2L) + whenever(getRubbishBinChildrenNode(underTest.state.value.rubbishBinHandle)).thenReturn( + listOf(megaNode1, megaNode2) + ) + whenever(getRubbishBinChildren(underTest.state.value.rubbishBinHandle)).thenReturn( + listOf(nodesListItem1, nodesListItem2) + ) + whenever(getCloudSortOrder()).thenReturn(SortOrder.ORDER_NONE) + whenever(isNodeDeletedFromBackupsUseCase(NodeId(1L))).thenReturn(true) + whenever(isNodeDeletedFromBackupsUseCase(NodeId(2L))).thenReturn(false) + + underTest.refreshNodes() + underTest.selectAllNodes() + underTest.onRestoreClicked() + + underTest.state.test { + Truth.assertThat(awaitItem().restoreType).isEqualTo(RestoreType.MOVE) + } + } + + @Test + fun `test that restoring nodes will execute the restore functionality when non backup nodes are selected`() = + runTest { + val nodesListItem1 = mock() + val nodesListItem2 = mock() + val megaNode1 = mock() + val megaNode2 = mock() + whenever(nodesListItem1.id.longValue).thenReturn(1L) + whenever(nodesListItem2.id.longValue).thenReturn(2L) + whenever(megaNode1.handle).thenReturn(1L) + whenever(megaNode2.handle).thenReturn(2L) + whenever(getRubbishBinChildrenNode(underTest.state.value.rubbishBinHandle)).thenReturn( + listOf(megaNode1, megaNode2) + ) + whenever(getRubbishBinChildren(underTest.state.value.rubbishBinHandle)).thenReturn( + listOf(nodesListItem1, nodesListItem2) + ) + whenever(getCloudSortOrder()).thenReturn(SortOrder.ORDER_NONE) + whenever(isNodeDeletedFromBackupsUseCase(NodeId(any()))).thenReturn(false) + + underTest.refreshNodes() + underTest.selectAllNodes() + underTest.onRestoreClicked() + + underTest.state.test { + Truth.assertThat(awaitItem().restoreType).isEqualTo(RestoreType.RESTORE) + } + } + + @Test + fun `test that acknowledging the restore functionality will reset the restore type`() = + runTest { + underTest.onRestoreHandled() + underTest.state.test { + Truth.assertThat(awaitItem().restoreType).isNull() + } + } + @After fun tearDown() { Dispatchers.resetMain() From f4a038d68b3f878cc8e6a6ba0cc58881d2d1d53f Mon Sep 17 00:00:00 2001 From: Rohit Soni Date: Wed, 10 May 2023 18:25:55 +1200 Subject: [PATCH 283/334] AND-15234: Fix NodeListViewUI Not Truncating Long Node Titles --- .../app/presentation/view/NodeListViewItem.kt | 111 +++++++++++------- .../app/presentation/view/NodesView.kt | 16 --- 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/view/NodeListViewItem.kt b/app/src/main/java/mega/privacy/android/app/presentation/view/NodeListViewItem.kt index 3fa5a131b56..e04012e03b5 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/view/NodeListViewItem.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/view/NodeListViewItem.kt @@ -1,13 +1,11 @@ -@file:OptIn(ExperimentalComposeUiApi::class) - package mega.privacy.android.app.presentation.view import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.absolutePadding import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -19,7 +17,6 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.ColorFilter @@ -29,6 +26,8 @@ import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.constraintlayout.compose.ConstraintLayout +import androidx.constraintlayout.compose.Dimension import coil.compose.rememberAsyncImagePainter import mega.privacy.android.app.R import mega.privacy.android.app.presentation.data.NodeUIItem @@ -104,47 +103,73 @@ internal fun NodeListViewItem( ) } } - Column(modifier = Modifier.padding(horizontal = 16.dp)) { - Row { - MiddleEllipsisText( - text = nodeUIItem.name, - style = MaterialTheme.typography.subtitle1, - color = if (nodeUIItem.isTakenDown) MaterialTheme.colors.red_800_red_400 else MaterialTheme.colors.textColorPrimary, - maxLines = 1 + Column( + modifier = Modifier.padding(start = 16.dp) + ) { + ConstraintLayout( + modifier = Modifier.fillMaxWidth() + ) { + val (nodeInfo, threeDots) = createRefs() + Image( + painter = painterResource(id = R.drawable.ic_dots_vertical_grey), + contentDescription = "3 dots", + modifier = Modifier + .constrainAs(threeDots) { + end.linkTo(parent.end) + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + } + .clickable { onMenuClick.invoke(nodeUIItem) } ) - val iconModifier = Modifier - .align(Alignment.CenterVertically) - .absolutePadding(left = 4.dp) - if (nodeUIItem.isFavourite) { - Image( - alignment = Alignment.Center, - modifier = iconModifier - .testTag(FAVORITE_TEST_TAG), - painter = painterResource(id = R.drawable.ic_favorite), - contentDescription = "Favorite", + Row(modifier = Modifier + .constrainAs(nodeInfo) { + top.linkTo(parent.top) + end.linkTo(threeDots.start) + start.linkTo(parent.start) + width = Dimension.fillToConstraints + } + .padding(end = 4.dp)) { + val iconModifier = Modifier + .align(Alignment.CenterVertically) + .absolutePadding(left = 4.dp) + MiddleEllipsisText( + text = nodeUIItem.name, + style = MaterialTheme.typography.subtitle1, + color = if (nodeUIItem.isTakenDown) MaterialTheme.colors.red_800_red_400 else MaterialTheme.colors.textColorPrimary, + maxLines = 1 + ) + if (nodeUIItem.isFavourite) { + Image( + alignment = Alignment.Center, + modifier = iconModifier + .testTag(FAVORITE_TEST_TAG), + painter = painterResource(id = R.drawable.ic_favorite), + contentDescription = "Favorite", + + ) + } + if (nodeUIItem.exportedData != null) { + Image( + alignment = Alignment.Center, + modifier = iconModifier + .testTag(EXPORTED_TEST_TAG), + painter = painterResource(id = R.drawable.link_ic), + contentDescription = "Link", + colorFilter = ColorFilter.tint( + MaterialTheme.colors.textColorSecondary + ) ) - } - if (nodeUIItem.exportedData != null) { - Image( - alignment = Alignment.Center, - modifier = iconModifier - .testTag(EXPORTED_TEST_TAG), - painter = painterResource(id = R.drawable.link_ic), - contentDescription = "Link", - colorFilter = ColorFilter.tint( - MaterialTheme.colors.textColorSecondary + } + if (nodeUIItem.isTakenDown) { + Image( + alignment = Alignment.Center, + modifier = iconModifier + .testTag(TAKEN_TEST_TAG), + painter = painterResource(id = R.drawable.ic_taken_down), + contentDescription = "Taken Down", ) - ) - } - if (nodeUIItem.isTakenDown) { - Image( - alignment = Alignment.Center, - modifier = iconModifier - .testTag(TAKEN_TEST_TAG), - painter = painterResource(id = R.drawable.ic_taken_down), - contentDescription = "Taken Down", - ) + } } } Text( @@ -175,9 +200,6 @@ internal fun NodeListViewItem( overflow = TextOverflow.Ellipsis ) } - - Spacer(modifier = Modifier.weight(1f)) - MenuItem(onMenuClick, nodeUIItem) } Divider( modifier = Modifier @@ -188,7 +210,6 @@ internal fun NodeListViewItem( } } -@OptIn(ExperimentalComposeUiApi::class) @Composable fun getFolderInfo(numFolders: Int, numFiles: Int): String { return if (numFolders == 0 && numFiles == 0) { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/view/NodesView.kt b/app/src/main/java/mega/privacy/android/app/presentation/view/NodesView.kt index 875695c39ab..5833cab89b2 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/view/NodesView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/view/NodesView.kt @@ -1,7 +1,5 @@ package mega.privacy.android.app.presentation.view -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.lazy.LazyColumn @@ -76,20 +74,6 @@ internal fun getPainter(nodeUIItem: FolderNode): Painter { } } -/** - * This method will create menu item image - * @param onMenuClick click listener of menu item - * @param nodeUIItem Node on which click has been performed - */ -@Composable -internal fun MenuItem(onMenuClick: (NodeUIItem) -> Unit, nodeUIItem: NodeUIItem) { - Image( - painter = painterResource(id = R.drawable.ic_dots_vertical_grey), - contentDescription = "3 dots", - modifier = Modifier.clickable { onMenuClick.invoke(nodeUIItem) } - ) -} - /** * This method will show [NodeUIItem] in Grid manner based on span * @param modifier From 99ba06b45151a8993361d7fb0f7c9326e886a90e Mon Sep 17 00:00:00 2001 From: Javier Gomez Date: Wed, 10 May 2023 09:58:12 +0200 Subject: [PATCH 284/334] Update available translations --- app/src/main/res/values-ar/strings.xml | 25 ++++------------------ app/src/main/res/values-de/strings.xml | 17 ++------------- app/src/main/res/values-es/strings.xml | 20 +++-------------- app/src/main/res/values-fr/strings.xml | 20 +++-------------- app/src/main/res/values-in/strings.xml | 16 ++------------ app/src/main/res/values-it/strings.xml | 18 ++-------------- app/src/main/res/values-ja/strings.xml | 16 ++------------ app/src/main/res/values-ko/strings.xml | 16 ++------------ app/src/main/res/values-nl/strings.xml | 17 ++------------- app/src/main/res/values-pl/strings.xml | 21 +++--------------- app/src/main/res/values-pt/strings.xml | 22 ++++--------------- app/src/main/res/values-ro/strings.xml | 20 +++-------------- app/src/main/res/values-ru/strings.xml | 19 ++-------------- app/src/main/res/values-th/strings.xml | 16 ++------------ app/src/main/res/values-vi/strings.xml | 18 +++------------- app/src/main/res/values-zh-rCN/strings.xml | 22 +++++-------------- app/src/main/res/values-zh-rTW/strings.xml | 20 ++++------------- app/src/main/res/values/strings.xml | 15 ------------- 18 files changed, 48 insertions(+), 290 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index b4178b0937e..a784d81f088 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -449,15 +449,6 @@ الرجاء اختيار وجهة المجلد تم النقل - - - تم نقل %d عنصر - تم نقل %d عنصر - تم نقل %d عنصرين - تم نقل %d عناصر - تم نقل %d عنصراً - تم نقل %d عنصر - لم يتم نقل %d عنصر @@ -466,8 +457,6 @@ خطأ. لم يتم النقل مشاركة - - خطأ. %d مشاركات لم تكتمل. خطأ. فشل في إزالة المشاركة @@ -480,8 +469,6 @@ لم تتم إزالة %d عنصر تمت مغادرة %d ملف - - تم مغادرة المشاركة لم يتم مغادرة %d ملف @@ -2151,7 +2138,7 @@ هل ترغب في فتح متجر غوغل Google Play حتى تتمكن من تثبيت تطبيق المصادقة؟ - Google Play + متجر غوغل Google Play تحتاج الى تطبيق مصادقة لتفعيل المصادقة المزدوجة 2FA على ميغا MEGA. يمكنك تنزيل وتثبيت أحد التطبيقات Google Authenticator، Duo Mobile أو Microsoft Authenticator app على موبايلك أو جهازك اللوحي. @@ -4293,10 +4280,6 @@ في انتظار انضمام الآخرين… [B]لم يتم [/B] [B]العثور على أي [/B] [A]مفضلات[/A] - - ابدأ اجتماع - - تحدث بأمان وخصوصية في مكالمات الصوت أو الفيديو مع الأصدقاء والزملاء في جميع أنحاء العالم. ابدأ المحادثة الآن @@ -4569,8 +4552,6 @@ قام [A]%s بتحديث[/A][B] أحد التكرارات إلى: [/B] قام [A]%s بتحديث[/A][B] وقت الاجتماع المتكرر[/B] - - قام [A]%s بإلغاء[/A][B] الاجتماع و كل تكراراته[/B] @@ -5549,7 +5530,7 @@ أدخل أقل من 30 محرفاً - Enter fewer than 3,000 characters + أدخل أقل من 3000 محرفاً تم تحديد موعد الاجتماع @@ -5636,4 +5617,6 @@ تعذرت إزالة %1$d عنصراً نهائيًا تعذرت إزالة %1$d عنصر نهائيًا + + Enter time \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index dccf9acc612..81858c0740d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -413,11 +413,6 @@ Bitte wählen Sie einen Zielordner Verschoben - - - %d Element verschoben - %d Elemente verschoben - %d Elemente wurden nicht verschoben @@ -426,8 +421,6 @@ Fehler. Nicht verschoben Freigegeben - - Fehler. %d Ordnerfreigaben konnten nicht erstellt werden. Fehler. Freigabe konnte nicht entfernt werden. @@ -440,8 +433,6 @@ %d Elemente wurden nicht entfernt %d Ordner verlassen - - Ordnerfreigabe verlassen %d Ordner nicht verlassen @@ -3877,10 +3868,6 @@ Warten auf weitere Teilnehmer [B]Keine[/B] [A]Favoriten[/A] [B]gefunden[/B] - - Meeting starten - - Führen Sie sichere und private Audio- oder Videogespräche mit Freunden und Kollegen auf der ganzen Welt. Jetzt chatten @@ -4129,8 +4116,6 @@ [A]%s [/A][B]hat einen Termin wie folgt geändert:[/B] [A]%s [/A][B]hat die Uhrzeit des wiederkehrenden Meetings geändert[/B] - - [A]%s [/A][B]hat alle Termine des wiederkehrenden Meetings abgesagt[/B] @@ -4792,4 +4777,6 @@ %1$d Element konnte nicht unwiderruflich gelöscht werden %1$d Elemente konnten nicht unwiderruflich gelöscht werden + + Uhrzeit eingeben \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 153b1940ff3..b7eb50f49a1 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -422,12 +422,6 @@ Elige una carpeta de destino Movido - - - Se ha movido %d elemento - Se ha movido %d de elementos - Se han movido %d elementos - No se han podido mover %d elementos @@ -436,8 +430,6 @@ Error. No se ha podido mover Compartido - - Error. No se han podido completar %d comparticiones Error. No se ha podido eliminar la compartición @@ -450,8 +442,6 @@ No se han podido eliminar %d elementos Se han abandonado %d carpetas - - Carpeta abandonada No se han podido abandonar %d carpetas @@ -3981,10 +3971,6 @@ Esperando a que otros se unan [B]No[/B] [B]hay[/B] [A]favoritos[/A] - - Empezar una reunión - - Hable de forma segura y privada en llamadas y videollamadas con amigos y colegas de todo el mundo. Empieza a chatear @@ -4239,8 +4225,6 @@ [A]%s ha actualizado[/A][B] un evento al:[/B] [A]%s ha actualizado[/A][B] la hora de la reunión periódica[/B] - - [A]%s ha cancelado[/A][B] la reunión y todos los eventos correspondientes[/B] @@ -4940,7 +4924,7 @@ Introducir menos de 30 caracteres - Enter fewer than 3,000 characters + Introducir menos de 3000 caracteres Reunión programada @@ -5003,4 +4987,6 @@ no se han podido eliminar %1$d elementos no se han podido eliminar %1$d elementos + + Introducir hora \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 039a5207cdd..9e9cbd72fb8 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -422,12 +422,6 @@ Veuillez choisir un dossier de destination A été déplacé - - - %d élément a été déplacé - %d d’éléments ont été déplacés - %d éléments ont été déplacés - %d éléments n’ont pas été déplacés @@ -436,8 +430,6 @@ Erreur. N’a pas été déplacé Partagé - - Erreur. %d partages n’ont pas été mis en place Erreur. Échec de retrait du partage @@ -450,8 +442,6 @@ %d éléments n’ont pas été supprimés Vous avez quitté %d dossiers - - Vous avez quitté le partage Vous n’avez pas quitté %d dossiers @@ -3981,10 +3971,6 @@ Nous attendons que d’autres se joignent [B]Aucun[/B] [A]favori[/A] [B]n’a été trouvé[/B] - - Commencer une réunion - - Parlez en toute sécurité et confidentialité lors d’appels vocaux ou vidéo avec vos amis et collègues partout dans le monde. Commencer à dialoguer maintenant @@ -4239,8 +4225,6 @@ [A]%s a mis à jour[/A][B] une occurrence :[/B] [A]%s a mis à jour[/A][B] l’heure de la réunion périodique[/B] - - [A]%s a annulé[/A][B] la réunion et toutes ses occurrences[/B] @@ -4940,7 +4924,7 @@ Saisir moins de 30 caractères - Enter fewer than 3,000 characters + Saisir moins de 3 000 caractères La réunion a été planifiée @@ -5003,4 +4987,6 @@ impossible de supprimer %1$d d’éléments définitivement impossible de supprimer %1$d éléments définitivement + + Saisir l’heure \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index ac4b13f3bf0..cae94480609 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -404,10 +404,6 @@ Silahkan pilih folder tujuan Dipindahkan - - - Memindahkan %d barang - %d barang tidak dipindahkan @@ -416,8 +412,6 @@ Terjadi kesalahan. Belum dipindahkan Berbagi - - Terjadi kesalahan. %d berbagi belum diselesaikan Terjadi kesalahan. Gagal menghilangkan berbagi @@ -430,8 +424,6 @@ %d item tidak dihapus %d folder tersisa - - Berbagi ditinggalkan %d folder tidak tersisa @@ -3773,10 +3765,6 @@ Menunggu yang lain untuk bergabung [B]Tidak ada[/B] [A]favorit[/A] [B]ditemukan[/B] - - Mulai rapat - - Bicaralah dengan aman dan pribadi di panggilan audio atau video dengan teman dan kolega di seluruh dunia. Mulai mengobrol sekarang @@ -4019,8 +4007,6 @@ [A]%s diperbarui[/A][B] kejadian untuk:[/B] [A]%s diperbarui[/A][B] waktu pertemuan berulang[/B] - - [A]%s dibatalkan[/A][B] pertemuan dan segala kejadiannya[/B] @@ -4581,4 +4567,6 @@ tidak dapat hapus selamanya %1$d barang + + Enter time \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 5b2f811cf73..6f5dc82cfd0 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -422,12 +422,6 @@ Per favore, scegli una cartella di destinazione Spostato - - - %d oggetto spostato - %d di oggetti spostati - %d oggetti spostati - %d oggetti non sono stati spostati @@ -436,8 +430,6 @@ Errore. Spostamento fallito Condiviso - - Errore. %d condivisioni non sono state completate Errore. Condivisione non rimossa @@ -450,8 +442,6 @@ %d oggetti non sono stati rimossi %d cartelle abbandonate - - Condivisione abbandonata %d cartelle non sono state abbandonate @@ -3981,10 +3971,6 @@ In attesa che si uniscano altre persone [B]Nessun[/B] [A]preferito[/A] [B]trovato[/B] - - Inizia un meeting - - Parla in sicurezza e privatamente in chiamate audio o video con amici e colleghi da tutto il mondo. Inizia a chattare ora @@ -4239,8 +4225,6 @@ [A]%s ha aggiornato[/A][B] un evento di:[/B] [A]%s ha aggiornato[/A][B] l\’orario del meeting ricorrente[/B] - - [A]%s ha annullato[/A][B] il meeting e tutti gli eventi relativi[/B] @@ -5003,4 +4987,6 @@ impossibile rimuovere permanentemente %1$d di oggetti impossibile rimuovere permanentemente %1$d oggetti + + Enter time \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 644e3f42355..a3dca92e081 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -404,10 +404,6 @@ 目的のフォルダを選択してください 移動しました - - - %d項目移動しました - %d個の項目は移動されませんでした @@ -416,8 +412,6 @@ エラー。移動されませんでした 共有済み - - エラー。%d件の共有は完了しませんでした エラー。共有を削除できませんでした @@ -430,8 +424,6 @@ %d個の項目は削除されませんでした %d個のフォルダから離れました - - 共有が残りました %d個のフォルダから離れませんでした @@ -3773,10 +3765,6 @@ 他の人が参加するのを待機中 [B][/B][A]お気に入り[/A][B]が見つかりません[/B] - - ミーティングを開始 - - 世界中の友人や同僚と、安全かつプライベートな音声通話やビデオ通話ができます。 今すぐチャットを開始 @@ -4019,8 +4007,6 @@ [B]開催日時を[/B][A]%sさんが更新しました[/A]: [B]定期的なミーティングの時間を[/B][A]%sさんが更新しました[/A] - - [B]以下のミーティングとそのすべての開催を[/B][A]%sさんがキャンセルしました[/A]: @@ -4581,4 +4567,6 @@ %1$d個の項目を恒久的に削除することはできませんでした + + 時間を入力してください \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 10492547db1..c4ae159f84a 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -404,10 +404,6 @@ 대상 폴더를 선택하세요 옮겨짐 - - - %d개 항목 이동함 - 항목 %d개가 옮겨지지 않았습니다 @@ -416,8 +412,6 @@ 오류. 이동되지 않았습니다 공유됨 - - 오류. %d개의 공유가 완료되지 않았습니다 오류. 공유가 제거되지 않았습니다 @@ -430,8 +424,6 @@ 항목 %d개가 제거되지 않음 폴더 %d개 남음 - - 공유 떠남 폴더 %d개를 떠날 수 없습니다 @@ -3773,10 +3765,6 @@ 다른 사람들이 참여하기를 기다리는 중 [B]발견된[/B] [A]즐겨찾기[/A] [B]없음[/B] - - 회의 시작 - - 전세계의 친구 및 동료들과 안전하고 비밀스럽게 음성 또는 영상 통화를 하세요. 지금 대화 시작 @@ -4019,8 +4007,6 @@ [A]%s 님이 되풀이 항목을 수정[/A][B]하였습니다[/B] [A]%s 님이 반복 회의 시간을 수정[/A][B]하였습니다[/B] - - [A]%s updated[/A][B] the recurring meeting frequency[/B] [A]%s[/A] 님이 [B]반복 회의를 취소하였습니다[/B] @@ -4581,4 +4567,6 @@ %1$d개의 항목을 영구적으로 삭제하지 못 했습니다 + + Enter time \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index f14ca146e0a..fdf2d051603 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -413,11 +413,6 @@ Kies een bestemmingsmap Verplaatst - - - Verplaatst %d item - Verplaatst %d items - %d items waren niet verplaatst @@ -426,8 +421,6 @@ Foutmelding. Niet verplaatst Gedeeld - - Fout. %d delingen waren niet voltooid Fout. Deling is mislukt te verwijderen @@ -440,8 +433,6 @@ %d items waren niet verplaatst %d mappen verlaten - - Deling is verlaten %d mappen waren niet verlaten @@ -3877,10 +3868,6 @@ Wachten voor andere om aan te sluiten [B]Geen[/B] [A]favorieten[/A] [B]gevonden[/B] - - Begin vergadering - - Praat veilig en privé tijdens audio- of videogesprekken met vrienden en collega\’s over de hele wereld. Begin nu met chatten @@ -4129,8 +4116,6 @@ [A]%sheeft [B]een gebeurtenis [/B] bijgewerkt naar:[/A] [A]%sheeft de [B]herhalende vergadertijd [/B] bijgewerkt [/A] - - [A]%sheeft [B]de vergadering en alle gebeurtenissen ervan [/B]geannuleerd[/A] @@ -4792,4 +4777,6 @@ Kon niet permanent %1$d item verwijderen kon niet permanent %1$d items verwijderen + + Voer tijd in \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index e57096a9a55..1129e39c135 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -431,13 +431,6 @@ Wybierz katalog docelowy Przeniesiono - - - Przeniesiono %d element - Przeniesiono %d elementów - Przeniesiono %d elementów - Przeniesiono %d elementów - %d elementów nie zostało przeniesione @@ -446,8 +439,6 @@ Błąd. Nie przeniesiono Udostępnione - - Błąd. %d udostępnień nie zostało zakończone Błąd. Nie usunięte udostępnienia @@ -460,8 +451,6 @@ %d elementów nie zostało usuniętych %d katalogów opuszczonych - - Pozostawione %d katalogów nie zostało opuszczonych @@ -4085,10 +4074,6 @@ Oczekiwanie na pozostałych [B]Nie[/B] [B]znaleziono[/B] [A]ulubionych[/A] - - Rozpocznij spotkanie - - Bezpieczne i prywatne rozmowy audio lub wideo z przyjaciółmi i współpracownikami z całego świata. Zacznij czatować teraz @@ -4349,8 +4334,6 @@ [A]%s zaktualizował[/A][B] czas spotkania na:[/B] [A]%s zaktualizował[/A][B] czas spotkania rekurencyjnego[/B] - - [A]%s odwołał[/A][B] spotkanie i wszystkie jego wystąpienia[/B] @@ -5143,7 +5126,7 @@ Wpisz mniej niż 30 znaków - Enter fewer than 3,000 characters + Wpisz mniej niż 3,000   znaków Spotkanie zostało zaplanowane @@ -5214,4 +5197,6 @@ nie można było usunąć całkowicie %1$d elementów nie można było usunąć całkowicie %1$d elementów + + Wprowadź czas \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index d97a0611c8a..237674fa445 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -422,12 +422,6 @@ Escolha a pasta de destino Movido - - - %d item foi movido - %d de itens foram movidos - %d itens foram movidos - %d itens não foram movidos @@ -436,8 +430,6 @@ Erro. Não foi movido Compartilhar - - Erro. %d compartilhamentos não foram concluídos Erro. Não foi possível remover o compartilhamento @@ -450,8 +442,6 @@ %d itens não foram eliminados Você saiu de %d pastas compartilhadas. - - Saiu do compartilhamento Você não saiu de %d pastas compartilhadas. @@ -481,7 +471,7 @@ Anual - Compartilhamento removido corretamente + Compartilhamento removido Nova pasta @@ -3981,10 +3971,6 @@ Esperando que as outras pessoas se conectem [B]Não[/B] [B]há[/B] [A]favoritos[/A] - - Fazer uma reunião - - Converse com segurança e privacidade por meio de chamadas de áudio ou de vídeo com amigos e colegas do mundo inteiro. Comece já a usar o chat @@ -4239,8 +4225,6 @@ [A]%s atualizou[/A][B] uma repetição para:[/B] [A]%s atualizou[/A][B] a hora da reunião periódica[/B] - - [A]%s cancelou[/A][B] a reunião e todas as suas repetições[/B] @@ -4940,7 +4924,7 @@ Digite menos de 30 caracteres - Enter fewer than 3,000 characters + Digite menos de 3000 caracteres Reunião agendada @@ -5003,4 +4987,6 @@ mas não foi possível eliminar permanentemente %1$d de itens mas não foi possível eliminar permanentemente %1$d itens + + Digite a hora \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 2554abffd34..2a4819b33e8 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -422,12 +422,6 @@ Te rugăm să selectezi un folder de destinație Moved - - - %d element a fost mutat - %d elemente au fost mutate - %d de elemente au fost mutate - %d items weren’t moved @@ -436,8 +430,6 @@ Eroare. Nu a fost mutat Partajate - - Eroare. %d partajări nu au fost încheiate Eroare. Partajarea a eșuat să fie eliminată @@ -450,8 +442,6 @@ %d items weren’t removed %d folders left - - Partajare părăsită %d folders weren’t left @@ -780,7 +770,7 @@ Fă backup cheii de recuperare - Parola îți deblochează cheia de recuperare + Parola îți deblochează Cheia de recuperare Datele tale pot fi citite doar printr-un lanț de operații de decriptare care începe cu cheia principală de criptare, pe care o stocăm criptată cu parola ta. Acest lucru înseamnă că dacă îți pierzi parola, cheia de recuperare nu îți mai poate fi decriptată și nu mai poți să-ți decriptezi datele. @@ -3972,10 +3962,6 @@ Se așteaptă ca alții să se alăture [B]Niciun[/B] [A]favorit[/A] [B]găsit[/B] - - Începe o întâlnire - - Discută securizat și privat în apeluri audio sau video cu prieteni și colegi din întreaga lume. Începe să chatuiești acum @@ -4229,8 +4215,6 @@ [A]%s a actualizat[/A][B] o ocurență:[/B] [A]%s a actualizat[/A][B] ora întâlnirii periodice[/B] - - [A]%s cancelled[/A][B] the meeting and all its occurrences[/B] @@ -4968,4 +4952,6 @@ couldn’t permanently remove %1$d item couldn’t permanently remove %1$d items + + Enter time \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b9e4bbfb322..518408dda08 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -431,13 +431,6 @@ Выберите папку назначения Перемещено - - - Перемещён %d элемент - Перемещено %d элемента - Перемещено %d элементов - Перемещено %d элемента - Не удалось переместить элементы (%d) @@ -446,8 +439,6 @@ Ошибка. Не перемещено Общий доступ открыт - - Ошибка. Доступ не был открыт (%d) Ошибка. Не удалось удалить @@ -460,8 +451,6 @@ Не удалось удалить элементы (%d) Папки оставлены (%d) - - Покинут общий элемент Папки не были оставлены (%d) @@ -4085,10 +4074,6 @@ Ждем, пока остальные присоединятся [B][/B][A]Избранное[/A] [B]не найдено[/B] - - Начните встречу - - Безопасно и конфиденциально разговаривайте в аудио- или видеозвонках с друзьями и коллегами по всему миру. Начните общаться сейчас @@ -4349,8 +4334,6 @@ [A]%s изменил[/A][B] повторение на:[/B] [A]%s изменил[/A][B] время повторяющейся встречи[/B] - - [A]%s отменил[/A][B] встречу и все её повторения[/B] @@ -5214,4 +5197,6 @@ %1$d элементов не удалось удалить %1$d элемента не удалось удалить + + Введите время \ No newline at end of file diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 641db8073d4..c5e83b2ef2c 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -404,10 +404,6 @@ กรุณาเลือกโฟลเดอร์ปลายทาง ย้ายแล้ว - - - ย้ายแล้ว %d รายการ - มี %d รายการที่ไม่ได้ถูกย้าย @@ -416,8 +412,6 @@ ผิดพลาด ยังไม่ได้ย้าย แชร์เรียบร้อยแล้ว - - ผิดพลาด แชร์ %d ไม่สำเร็จ ผิดพลาด ลบการแชร์ไม่สำเร็จ @@ -430,8 +424,6 @@ มี %d รายการที่ไม่ได้ถูกย้าย ออกจากการแชร์แล้ว %d โฟลเดอร์ - - แชร์ที่เหลือ ยังไม่ได้ออกจากแชร์ %d โฟลเดอร์ @@ -3773,10 +3765,6 @@ กำลังรอให้คนอื่นเข้าร่วม [B]ไม่[/B][B]พบ[/B][A]รายการโปรด[/A] - - เริ่มการประชุม - - โทรด้วยเสียงหรือวิดีโอที่ปลอดภัยและเป็นส่วนตัวกับเพื่อนและเพื่อนร่วมงานทั่วโลก เริ่มแชทได้เลย @@ -4019,8 +4007,6 @@ [A]%s อัปเดต[/A][B] เหตุการณ์ใหม่แล้ว:[/B] [A]%s อัปเดต[/A][B]เวลาการประชุมที่เป็นกิจวัตรใหม่แล้ว:[/B] - - [A]%s ยกเลิก[/A][B]การประชุมและเหตุการณ์อื่น ๆ ทั้งหมดแล้ว[/B] @@ -4581,4 +4567,6 @@ และมีอีก %1$d รายการที่ไม่สามารถลบออกอย่างถาวรได้ + + Enter time \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 613bfef3ad8..1a2650c5a7b 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -404,10 +404,6 @@ Xin chọn thư mục đích Đã di chuyển - - - Đã di chuyển %d mục - Đã không di chuyển được %d mục @@ -416,8 +412,6 @@ Lỗi. Chưa di chuyển được Mục Chia Sẻ - - Phát sinh lỗi. %d mục chia sẻ chưa được hoàn tất Lỗi. Loại bỏ mục chia sẻ thất bại @@ -430,8 +424,6 @@ Đã không loại bỏ được %d mục Đã rời khỏi %d thư mục - - Đã rời khỏi chia sẻ Đã không rời khỏi được %d thư mục @@ -3773,10 +3765,6 @@ Đang chờ người khác tham gia [B]Không có[/B] [A]mục ưa thích[/A] [B]nào[/B] - - Bắt đầu một cuộc họp - - Nói chuyện an toàn và riêng tư vớI gọi thoại hay gọi video với bạn bè và đồng nghiệp trên toàn cầu. Bắt đầu trò chuyện ngay @@ -4019,8 +4007,6 @@ [A]%s đã cập nhật[/A][B] một thời điểm tái diễn của cuộc họp thành:[/B] [A]%s đã cập nhật[/A][B] thời gian của cuộc họp định kỳ[/B] - - [A]%s đã hủy[/A][B] cuộc họp và các tái diễn trong tương lai[/B] @@ -4534,7 +4520,7 @@ Nhập ít hơn 30 ký tự - Enter fewer than 3,000 characters + Nhập ít hơn 3 000 ký tự Cuộc họp được lên lịch @@ -4581,4 +4567,6 @@ không thể xóa vĩnh viễn %1$d mục + + Nhập thời gian \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 53daf93ea1f..db66ffe92ad 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -404,10 +404,6 @@ 请选择一个目标文件夹 已移动 - - - 已移动%d个项目 - %d个项目移动失败 @@ -416,8 +412,6 @@ 错误。未移动 共享 - - 发生错误。%d个共享未完成 发生错误。移除共享失败 @@ -430,8 +424,6 @@ %d个项目移除失败 已离开%d文件夹 - - 已离开该共享 %d文件夹离开失败 @@ -2340,7 +2332,7 @@ 链接已复制到剪贴板 - %s/[A]月[/A] *[A]起[/A] + %s/[A]月[/A] [A]起[/A]* * 续订日期到期前随时可取消续订服务。 @@ -3773,10 +3765,6 @@ 正在等待其他人加入 [B]未[/B][B]找到[/B][A]收藏[/A] - - 发起会议 - - 使用语音或视频通话与全世界的朋友和同事进行安全私密沟通。 立即开始聊天 @@ -4019,8 +4007,6 @@ [A]%s更新了[/A][B]一个事件到:[/B] [A]%s更新了[/A][B]定期会议时间[/B] - - [A]%s取消了[/A][B]会议以及它所有事件[/B] @@ -4532,9 +4518,9 @@ 会议名称为必填项 - 输入少于30 个字符 + 输入少于30 个字符(15个中文字) - 输入少于3,000 个字符 + 输入少于3,000 个字符(1,500个中文字) 已安排会议 @@ -4581,4 +4567,6 @@ 无法永久移除%1$d个项目 + + 输入时间 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 8be3478e4c4..47c2b2d31b2 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -404,10 +404,6 @@ 請選擇目的地資料夾 已移動 - - - 移動了%d個項目 - 未移動%d個項目 @@ -416,8 +412,6 @@ 錯誤。未移動 共享 - - 發生錯誤。尚未完成%d個共享 發生錯誤,移除共享失敗 @@ -430,8 +424,6 @@ 未移除%d個項目 留下%d個資料夾 - - 已離開該共享 未留下%d個資料夾 @@ -3773,10 +3765,6 @@ 正在等待其他人加入 [B]未[/B][B]找到[/B][A]收藏[/A] - - 開始會議 - - 透過語音或視訊與全世界的朋友和同事們進行安全私密交談。 開始對話 @@ -4019,8 +4007,6 @@ [A]%s更新[/A][B]一個事件:[/B] [A]%s更新[/A][B]定期性會議時間[/B] - - [A]%s取消[/A][B]會議和其所有事件[/B] @@ -4532,9 +4518,9 @@ 會議名稱為必填 - 輸入少於30 個字元 + 輸入少於30 個字元(15個中文字) - Enter fewer than 3,000 characters + 輸入少於3,000個字元(1,500個中文字)  已安排會議 @@ -4581,4 +4567,6 @@ 無法永久移除%1$d個項目 + + 輸入時間 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4e05f94643c..87e7794fc6b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -413,11 +413,6 @@ Please choose a destination folder Moved - - - Moved %d item - Moved %d items - %d items weren’t moved @@ -426,8 +421,6 @@ Error. Not moved Shared - - Error. %d shares were not completed Error. Share failed to remove @@ -440,8 +433,6 @@ %d items weren’t removed %d folders left - - Share left %d folders weren’t left @@ -3877,10 +3868,6 @@ Waiting for others to join [B]No[/B] [A]favourites[/A] [B]found[/B] - - Start a meeting - - Talk securely and privately on audio or video calls with friends and colleagues around the world. Start chatting now @@ -4129,8 +4116,6 @@ [A]%s updated[/A][B] an occurrence to:[/B] [A]%s updated[/A][B] the recurring meeting time[/B] - - [A]%s updated[/A][B] the recurring meeting frequency[/B] [A]%s cancelled[/A][B] the meeting and all its occurrences[/B] From 34f5cfea1ddc072b6d1b54597fc55df83f366d48 Mon Sep 17 00:00:00 2001 From: Daniel Oosthuizen Date: Mon, 15 May 2023 10:30:27 +1200 Subject: [PATCH 285/334] AND-16456 - Fix crash on selecting a folder in search results --- .../android/app/presentation/search/SearchFragment.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt index 0a53d149fa9..43116b4d538 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt @@ -126,12 +126,12 @@ class SearchFragment : RotatableFragment() { private val binding: FragmentSearchBinding get() = _binding!! - private lateinit var gridLayoutManager: CustomizedGridLayoutManager + private var gridLayoutManager: CustomizedGridLayoutManager? = null private val outMetrics: DisplayMetrics by lazy { DisplayMetrics() } - private lateinit var layoutManager: LinearLayoutManager + private var layoutManager: LinearLayoutManager? = null private var trashIcon: MenuItem? = null @@ -814,9 +814,9 @@ class SearchFragment : RotatableFragment() { if (lastVisiblePosition >= 0) { if (state().currentViewType == ViewType.LIST) { - layoutManager.scrollToPositionWithOffset(lastVisiblePosition, 0) + layoutManager?.scrollToPositionWithOffset(lastVisiblePosition, 0) } else { - gridLayoutManager.scrollToPositionWithOffset(lastVisiblePosition, 0) + gridLayoutManager?.scrollToPositionWithOffset(lastVisiblePosition, 0) } } return 2 @@ -851,7 +851,7 @@ class SearchFragment : RotatableFragment() { var lastFirstVisiblePosition: Int if (state().currentViewType == ViewType.LIST) { lastFirstVisiblePosition = - layoutManager.findFirstCompletelyVisibleItemPosition() + layoutManager?.findFirstCompletelyVisibleItemPosition() ?: -1 } else { lastFirstVisiblePosition = (recyclerView as NewGridRecyclerView).findFirstCompletelyVisibleItemPosition() From 8c27c26f4c2a1ec239dc203ced0f9e31823a68b8 Mon Sep 17 00:00:00 2001 From: Daniel Oosthuizen Date: Mon, 15 May 2023 10:57:45 +1200 Subject: [PATCH 286/334] Update version to 8.1.1 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 072b56c3857..67be07c4cb9 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ task clean(type: Delete) { // Define versions in a single place ext { // App - appVersion = "8.1" + appVersion = "8.1.1" // Sdk and tools compileSdkVersion = 33 From c65bc107237e17c668da3380ddcabeb7c9917adc Mon Sep 17 00:00:00 2001 From: Veronika Koreiba Date: Wed, 17 May 2023 08:39:14 +1200 Subject: [PATCH 287/334] Update available translations --- app/src/main/res/values-ar/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-de/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-es/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-fr/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-in/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-it/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-ja/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-ko/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-nl/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-pl/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-pt/strings.xml | 18 +++++++++++++++--- app/src/main/res/values-ro/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-ru/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-th/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-vi/strings.xml | 18 +++++++++++++++--- app/src/main/res/values-zh-rCN/strings.xml | 16 ++++++++++++++-- app/src/main/res/values-zh-rTW/strings.xml | 16 ++++++++++++++-- app/src/main/res/values/strings.xml | 22 ++++++++++++++-------- 18 files changed, 254 insertions(+), 44 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index a784d81f088..acabc2e41de 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -5528,9 +5528,9 @@ اسم الاجتماع مطلوب - أدخل أقل من 30 محرفاً + Enter up to 30 characters - أدخل أقل من 3000 محرفاً + Enter up to 3,000 characters تم تحديد موعد الاجتماع @@ -5619,4 +5619,16 @@ Enter time + + دعوات الاجتماع + + + + + + + + يبدأ الاجتماع خلال 15 دقيقة + + يبدأ الاجتماع الآن \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 81858c0740d..ea69d1bba11 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -4720,9 +4720,9 @@ Der Meetingname ist erforderlich - Bitte weniger als 30 Zeichen eingeben + Enter up to 30 characters - Bitte weniger als 3.000 Zeichen eingeben + Enter up to 3,000 characters Meeting geplant @@ -4779,4 +4779,16 @@ Uhrzeit eingeben + + Meetingeinladungen + + + + + + + + Meeting beginnt in 15 Minuten + + Meeting beginnt jetzt \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index b7eb50f49a1..c47606fc782 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -4922,9 +4922,9 @@ Es necesario indicar el nombre de la reunión - Introducir menos de 30 caracteres + Enter up to 30 characters - Introducir menos de 3000 caracteres + Enter up to 3,000 characters Reunión programada @@ -4989,4 +4989,16 @@ Introducir hora + + Invitaciones a la reunión + + + + + + + + La reunión empieza en 15 minutos + + La reunión empieza ahora \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 9e9cbd72fb8..399ca0e8c33 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -4922,9 +4922,9 @@ Le nom de la réunion est requis - Saisir moins de 30 caractères + Saisir jusqu’à 30 caractères - Saisir moins de 3 000 caractères + Saisir jusqu’à 3 000 caractères La réunion a été planifiée @@ -4989,4 +4989,16 @@ Saisir l’heure + + Invitations à la réunion + + + + + + + + La réunion commence dans 15 minutes + + La réunion commence maintenant \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index cae94480609..b64af01ae60 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -4518,9 +4518,9 @@ Nama rapat wajib diisi - Masukan kurang dari 30 karakter + Enter up to 30 characters - Enter fewer than 3,000 characters + Enter up to 3,000 characters Rapat dijadwalkan @@ -4569,4 +4569,16 @@ Enter time + + Undangan rapat + + + + + + + + Rapat dimulai dalam 15 menit + + Rapat dimulai sekarang \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 6f5dc82cfd0..1303349903f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -4922,9 +4922,9 @@ Il nome del meeting è obbligatorio - Inserisci meno di 30 caratteri + Enter up to 30 characters - Enter fewer than 3,000 characters + Enter up to 3,000 characters Meeting programmato @@ -4989,4 +4989,16 @@ Enter time + + Inviti per il meeting + + + + + + + + Il meeting inizierà tra 15 minuti + + Il meeting inizia ora \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index a3dca92e081..72fa7afad6b 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -4518,9 +4518,9 @@ ミーティング名が必要です - 30 文字未満で入力してください + 30 文字以下で入力してください - 3000 文字未満で入力してください + 3000 文字以下で入力してください ミーティングがスケジュールされました @@ -4569,4 +4569,16 @@ 時間を入力してください + + ミーティング招待 + + + + + + + + ミーティングが15分後に開始します + + ミーティングが今から始まります \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index c4ae159f84a..6792d240d71 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -4518,9 +4518,9 @@ 회의 이름은 필수입니다 - 30자 이하로 입력하세요 + Enter up to 30 characters - Enter fewer than 3,000 characters + Enter up to 3,000 characters 회의가 예약되었습니다 @@ -4569,4 +4569,16 @@ Enter time + + 회의 초대 + + + + + + + + 15분 내에 회의가 시작됩니다 + + 회의가 지금 시작됩니다 \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index fdf2d051603..557987f52a9 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -4720,9 +4720,9 @@ Vergadernaam is vereist - Voer minder dan 30 tekens in + Voer tot 30 tekens in - Voer minder dan 3000 tekens in + Voer tot 3000 tekens in Vergadering gepland @@ -4779,4 +4779,16 @@ Voer tijd in + + Vergader uitnodigingen + + + + + + + + Vergadering begint over 15 minuten + + Vergadering begint nu \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 1129e39c135..9dc6d73bdb6 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -5124,9 +5124,9 @@ Nazwa spotkania jest wymagana - Wpisz mniej niż 30 znaków + Enter up to 30 characters - Wpisz mniej niż 3,000   znaków + Enter up to 3,000 characters Spotkanie zostało zaplanowane @@ -5199,4 +5199,16 @@ Wprowadź czas + + Powiadomienia o spotkaniach + + + + + + + + Spotkanie rozpoczyna się za 15 minut + + Spotkanie zaczyna się teraz \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 237674fa445..831370b2b97 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -298,7 +298,7 @@ - Envio iniciado + Upload iniciado %1$d uploads foram iniciados %1$d uploads foram iniciados @@ -4922,9 +4922,9 @@ O nome da reunião é obrigatório - Digite menos de 30 caracteres + Digite até 30 caracteres - Digite menos de 3000 caracteres + Digite até 3000 caracteres Reunião agendada @@ -4989,4 +4989,16 @@ Digite a hora + + Convites à reunião + + + + + + + + A reunião começa em 15 minutos. + + A reunião está começando \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 2a4819b33e8..e0b78ba263f 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -4895,9 +4895,9 @@ Este necesar numele întâlnirii - Introdu mai puțin de 30 de caractere + Enter up to 30 characters - Enter fewer than 3,000 characters + Enter up to 3,000 characters Meeting scheduled @@ -4954,4 +4954,16 @@ Enter time + + + + + + + + + + Meeting starts in 15 minutes + + Meeting starts now \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 518408dda08..a6e8945fa4c 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -5124,9 +5124,9 @@ Укажите название встречи - Введите меньше 30 символов + Введите до 30 символов - Введите меньше 3 000 символов + Введите до 3 000 символов Встреча запланирована @@ -5199,4 +5199,16 @@ Введите время + + Приглашения на встречу + + + + + + + + Встреча начнётся через 15 минут + + Встреча начинается \ No newline at end of file diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index c5e83b2ef2c..1deb010fc1e 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -4518,9 +4518,9 @@ ต้องระบุชื่อเรื่องการประชุม - ต้องกรอกตัวอักษรให้น้อยกว่า 30 ตัว + Enter up to 30 characters - Enter fewer than 3,000 characters + Enter up to 3,000 characters กำหนดการจัดประชุมแล้ว @@ -4569,4 +4569,16 @@ Enter time + + เชิญเข้าร่วมการประชุม + + + + + + + + การประชุมจะเริ่มในอีก 15 นาที + + การประชุมเริ่มขึ้นแล้ว \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 1a2650c5a7b..570cbeffd71 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1425,7 +1425,7 @@ Không dùng HTTP - Chỉ nên bật tính năng này khi các phiên truyền tải lên/xuống không hoạt động. Trong các trường hợp bình thường, giao thức HTTP rất thích hợp để truyền tải các nội dung mã hóa. + Chỉ nên bật khi các phiên truyền tải không lưu thông. Trong các trường hợp bình thường, HTTP là đủ đáp ứng vì các phiên truyền tải đã được mã hóa. Cách hoạt động @@ -4518,9 +4518,9 @@ Tên cho cuộc họp là bắt buộc - Nhập ít hơn 30 ký tự + Enter up to 30 characters - Nhập ít hơn 3 000 ký tự + Enter up to 3,000 characters Cuộc họp được lên lịch @@ -4569,4 +4569,16 @@ Nhập thời gian + + Mời vào cuộc họp + + + + + + + + Cuộc họp bắt đầu trong 15 phút + + Cuộc họp bắt đầu ngay bây giờ \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index db66ffe92ad..2f68bb88fd3 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -4518,9 +4518,9 @@ 会议名称为必填项 - 输入少于30 个字符(15个中文字) + 输入最多30 个字符(15个中文字) - 输入少于3,000 个字符(1,500个中文字) + 输入最多3,000 个字符(1,500个中文字) 已安排会议 @@ -4569,4 +4569,16 @@ 输入时间 + + 会议邀请 + + + + + + + + 会议将在15分钟后开始 + + 会议现在开始 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 47c2b2d31b2..8f39ffa1ff5 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -4518,9 +4518,9 @@ 會議名稱為必填 - 輸入少於30 個字元(15個中文字) + Enter up to 30 characters - 輸入少於3,000個字元(1,500個中文字)  + Enter up to 3,000 characters 已安排會議 @@ -4569,4 +4569,16 @@ 輸入時間 + + 會議邀請 + + + + + + + + 會議將在15分鐘後開始 + + 會議現在開始 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8d57284885d..0103cfb3ed0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4618,12 +4618,18 @@ Couldn’t copy %1$d item Couldn’t copy %1$d items - + Copied %1$d item,  Copied %1$d items,  - + couldn’t copy %1$d item couldn’t copy %1$d items @@ -4714,9 +4720,9 @@ Meeting name is required - Enter fewer than 30 characters + Enter up to 30 characters - Enter fewer than 3,000 characters + Enter up to 3,000 characters Meeting scheduled @@ -4773,10 +4779,6 @@ Enter time - - Meeting starts in 15 minutes - - Meeting starts now Meeting invitations @@ -4785,4 +4787,8 @@ Meeting reminders Notify me 15 minutes before the meetings starts and on the time the meeting is schedule to start + + Meeting starts in 15 minutes + + Meeting starts now \ No newline at end of file From eded92617847360ba1ac313eb7b5898372ea7063 Mon Sep 17 00:00:00 2001 From: Veronika Koreiba Date: Wed, 17 May 2023 08:41:47 +1200 Subject: [PATCH 288/334] Update version code - v8.2 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 373611a20c8..645aef5004a 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ task clean(type: Delete) { // Define versions in a single place ext { // App - appVersion = "8.1" + appVersion = "8.2" // Sdk and tools compileSdkVersion = 33 From 970da2750547af0b05fdef387bc2271a1843aa7e Mon Sep 17 00:00:00 2001 From: Veronika Koreiba Date: Wed, 17 May 2023 09:03:34 +1200 Subject: [PATCH 289/334] Update SDK and MEGAchat submodules --- sdk/src/main/jni/mega/sdk | 2 +- sdk/src/main/jni/megachat/sdk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index e05402cfff8..94e2b9dd1db 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit e05402cfff80483019f80f3d72a8906eb935d21d +Subproject commit 94e2b9dd1db6a886e21cc1ee826bda58c8c33f99 diff --git a/sdk/src/main/jni/megachat/sdk b/sdk/src/main/jni/megachat/sdk index f76eb037030..b84c9e70f4c 160000 --- a/sdk/src/main/jni/megachat/sdk +++ b/sdk/src/main/jni/megachat/sdk @@ -1 +1 @@ -Subproject commit f76eb037030beaaa86541cba99452a579fdea5a8 +Subproject commit b84c9e70f4cfff1410b04ef147927c261624d738 From ac0462a7298d43b152f7463084890288d34a183a Mon Sep 17 00:00:00 2001 From: Veronika Koreiba Date: Wed, 17 May 2023 09:39:21 +1200 Subject: [PATCH 290/334] Update sdk prebuilt version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 645aef5004a..8769ef136c2 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230512.122129-dev' + megaSdkVersion = '20230516.213653-rel' //JDK and Java Version jdk = "17" From d34a8e0f5253ff8c058aefac2447392e0860022b Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Wed, 17 May 2023 15:05:44 +0800 Subject: [PATCH 291/334] CC-4411 AND-The app is crash when adding subtitles MR link: https://code.developers.mega.co.nz/mobile/android/android/-/merge_requests/5466 --- .../app/mediaplayer/service/MediaPlayerServiceViewModel.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerServiceViewModel.kt b/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerServiceViewModel.kt index e5baf4807ad..849fea616cf 100644 --- a/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerServiceViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerServiceViewModel.kt @@ -92,7 +92,6 @@ import mega.privacy.android.data.mapper.FileDurationMapper import mega.privacy.android.domain.entity.SortOrder import mega.privacy.android.domain.entity.mediaplayer.PlaybackInformation import mega.privacy.android.domain.entity.mediaplayer.SubtitleFileInfo -import mega.privacy.android.domain.entity.node.NodeId import mega.privacy.android.domain.entity.node.TypedFileNode import mega.privacy.android.domain.entity.node.TypedNode import mega.privacy.android.domain.entity.statistics.MediaPlayerStatisticsEvents @@ -113,7 +112,6 @@ import mega.privacy.android.domain.usecase.GetLocalFolderLinkFromMegaApiUseCase import mega.privacy.android.domain.usecase.GetLocalLinkFromMegaApiUseCase import mega.privacy.android.domain.usecase.GetNodesByHandlesUseCase import mega.privacy.android.domain.usecase.GetParentNodeFromMegaApiFolderUseCase -import mega.privacy.android.domain.usecase.GetParentNodeUseCase import mega.privacy.android.domain.usecase.GetRootNodeFromMegaApiFolderUseCase import mega.privacy.android.domain.usecase.GetRootNodeUseCase import mega.privacy.android.domain.usecase.GetRubbishNodeUseCase @@ -186,7 +184,6 @@ class MediaPlayerServiceViewModel @Inject constructor( private val getThumbnailFromMegaApiUseCase: GetThumbnailFromMegaApiUseCase, private val getThumbnailFromMegaApiFolderUseCase: GetThumbnailFromMegaApiFolderUseCase, private val getInboxNodeUseCase: GetInboxNodeUseCase, - private val getParentNodeUseCase: GetParentNodeUseCase, private val getParentNodeFromMegaApiFolderUseCase: GetParentNodeFromMegaApiFolderUseCase, private val getRootNodeUseCase: GetRootNodeUseCase, private val getRootNodeFromMegaApiFolderUseCase: GetRootNodeFromMegaApiFolderUseCase, @@ -496,7 +493,7 @@ class MediaPlayerServiceViewModel @Inject constructor( else -> getRootNodeUseCase() } } else { - getParentNodeUseCase(NodeId(parentHandle)) + getUnTypedNodeByHandleUseCase(parentHandle) }?.let { parent -> if (parentHandle == INVALID_HANDLE) { context.getString( From 4a8c3f246fd678efef4846b636e39f2f6055b9bf Mon Sep 17 00:00:00 2001 From: Pau Dominkovics Coll Date: Thu, 18 May 2023 22:31:33 +1200 Subject: [PATCH 292/334] AND-16287 compose screen fixes --- .../presentation/fileinfo/FileInfoActivity.kt | 8 +++++ .../fileinfo/FileInfoViewModel.kt | 7 +---- .../fileinfo/view/FileInfoContent.kt | 2 +- .../fileinfo/view/FileInfoScreen.kt | 3 ++ .../fileinfo/view/FileInfoViewConstants.kt | 1 + .../fileinfo/view/FolderContentView.kt | 10 +++---- .../fileinfo/FileInfoViewModelTest.kt | 29 +++++++++++++++++++ 7 files changed, 48 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoActivity.kt b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoActivity.kt index e12138f8963..0f2c8b5a5f7 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoActivity.kt @@ -11,6 +11,10 @@ import androidx.activity.viewModels import androidx.compose.material.SnackbarHostState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.core.view.WindowCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle import dagger.hilt.android.AndroidEntryPoint @@ -111,6 +115,7 @@ class FileInfoActivity : BaseActivity() { /** * on create the activity */ + @OptIn(ExperimentalComposeUiApi::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) @@ -156,6 +161,9 @@ class FileInfoActivity : BaseActivity() { onShowMoreSharedWithContactsClick = this::navigateToSharedContacts, onPublicLinkCopyClick = viewModel::copyPublicLink, onMenuActionClick = { handleAction(it, uiState) }, + modifier = Modifier.semantics { + testTagsAsResourceId = true + } ) uiState.requiredExtraAction?.let { action -> ExtraActionDialog( diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoViewModel.kt index c23fb6bd595..d5b80ea8cc3 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/FileInfoViewModel.kt @@ -743,13 +743,8 @@ class FileInfoViewModel @Inject constructor( viewModelScope.launch { runCatching { getFolderTreeInfo(folder).let { folderTreeInfo -> - val actions = getAvailableNodeActionsUseCase(typedNode) - .map { nodeAction -> nodeActionMapper(nodeAction) } _uiState.update { - it.copy( - actions = actions, - folderTreeInfo = folderTreeInfo - ) + it.copyWithFolderTreeInfo(folderTreeInfo) } } }.onFailure { diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoContent.kt b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoContent.kt index d9656a8a20d..b3f1a248086 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoContent.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoContent.kt @@ -139,7 +139,7 @@ internal fun FileInfoContent( //folder content folderTreeInfo?.let { FolderContentView( - numberOfFolders = it.numberOfFolders, + numberOfFolders = it.numberOfFolders - 1, //we don't want to count itself numberOfFiles = it.numberOfFiles, modifier = paddingHorizontal, ) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoScreen.kt b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoScreen.kt index 7d0ea06e38b..2148d34525c 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoScreen.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoScreen.kt @@ -150,6 +150,7 @@ internal fun FileInfoScreen( ) } else { FileInfoTopBar( + modifier = Modifier.testTag(TEST_TAG_TOP_APPBAR), title = viewState.title, actions = viewState.actions, tintColor = tintColor, @@ -243,10 +244,12 @@ private fun FileInfoScreenPreview( FileInfoMenuAction.SelectionModeAction.ClearSelection -> { state = state.copy(outShareContactsSelected = emptyList()) } + FileInfoMenuAction.SelectionModeAction.SelectAll -> { state = state.copy( outShareContactsSelected = state.outSharesCoerceMax.map { it.contactItem.email }) } + else -> {} } }, diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoViewConstants.kt b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoViewConstants.kt index ab88ca53e13..9b906a5d607 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoViewConstants.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoViewConstants.kt @@ -3,6 +3,7 @@ package mega.privacy.android.app.presentation.fileinfo.view internal const val verticalSpace = 20 internal const val paddingStartDefault = 72 +internal const val TEST_TAG_TOP_APPBAR = "topAppBar" internal const val TEST_TAG_AVAILABLE_OFFLINE_SWITCH = "switch" internal const val TEST_TAG_CREATION_TIME = "creationTime" internal const val TEST_TAG_MODIFICATION_TIME = "modificationTime" diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FolderContentView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FolderContentView.kt index 4a862b93cb6..a743235e7b5 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FolderContentView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FolderContentView.kt @@ -3,7 +3,6 @@ package mega.privacy.android.app.presentation.fileinfo.view import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource @@ -14,7 +13,6 @@ import mega.privacy.android.core.ui.theme.AndroidTheme /** * Shows a titled text with the total amount of files and folders for a folder */ -@OptIn(ExperimentalComposeUiApi::class) @Composable internal fun FolderContentView( numberOfFolders: Int, @@ -23,15 +21,17 @@ internal fun FolderContentView( ) = FileInfoTitledText( title = stringResource(R.string.file_properties_info_content), text = when { - numberOfFolders == 0 && numberOfFiles == 0 -> stringResource(R.string.file_browser_empty_folder) - numberOfFolders == 0 && numberOfFiles > 0 -> + numberOfFolders <= 0 && numberOfFiles <= 0 -> stringResource(R.string.file_browser_empty_folder) + numberOfFolders <= 0 && numberOfFiles > 0 -> pluralStringResource(R.plurals.num_files_with_parameter, numberOfFiles, numberOfFiles) - numberOfFiles == 0 && numberOfFolders > 0 -> + + numberOfFiles <= 0 -> pluralStringResource( R.plurals.num_folders_with_parameter, numberOfFolders, numberOfFolders ) + else -> { pluralStringResource( R.plurals.num_folders_num_files, diff --git a/app/src/test/java/test/mega/privacy/android/app/presentation/fileinfo/FileInfoViewModelTest.kt b/app/src/test/java/test/mega/privacy/android/app/presentation/fileinfo/FileInfoViewModelTest.kt index f869ceb50d8..2e68b53a867 100644 --- a/app/src/test/java/test/mega/privacy/android/app/presentation/fileinfo/FileInfoViewModelTest.kt +++ b/app/src/test/java/test/mega/privacy/android/app/presentation/fileinfo/FileInfoViewModelTest.kt @@ -35,6 +35,7 @@ import mega.privacy.android.app.utils.wrapper.FileUtilWrapper import mega.privacy.android.data.gateway.ClipboardGateway import mega.privacy.android.data.repository.MegaNodeRepository import mega.privacy.android.domain.entity.EventType +import mega.privacy.android.domain.entity.FolderTreeInfo import mega.privacy.android.domain.entity.StorageState import mega.privacy.android.domain.entity.StorageStateEvent import mega.privacy.android.domain.entity.contacts.ContactPermission @@ -217,6 +218,7 @@ internal class FileInfoViewModelTest { whenever(getNodeVersionsByHandle(nodeId)).thenReturn(null) whenever(monitorNodeUpdatesById.invoke(nodeId)).thenReturn(emptyFlow()) whenever(monitorChildrenUpdates.invoke(nodeId)).thenReturn(emptyFlow()) + whenever(monitorOnlineStatusUpdates.invoke()).thenReturn(emptyFlow()) whenever(monitorContactUpdates.invoke()).thenReturn(emptyFlow()) whenever(fileUtilWrapper.getFileIfExists(null, thumbUri)) .thenReturn(File(null as File?, thumbUri)) @@ -228,6 +230,8 @@ internal class FileInfoViewModelTest { whenever(typedFileNode.hasPreview).thenReturn(false) whenever(isAvailableOffline.invoke(any())).thenReturn(true) whenever(getAvailableNodeActionsUseCase(any())).thenReturn(emptyList()) + whenever(getNodeOutSharesUseCase(nodeId)).thenReturn(emptyList()) + whenever(getOutShares(nodeId)).thenReturn(emptyList()) } @After @@ -881,6 +885,31 @@ internal class FileInfoViewModelTest { } } + @Test + fun `test that when folder tree info is received then total size, folder content and available offline is updated correctly`() = + runTest { + val actualSize = 1024L + val versionsSize = 512L + val folderTreeInfo = FolderTreeInfo(1, 2, actualSize, 1, versionsSize) + val folderNode = mock { + on { id }.thenReturn(nodeId) + on { name }.thenReturn("Folder name") + }.also { folderNode -> + whenever(getNodeByIdUseCase.invoke(nodeId)).thenReturn(folderNode) + whenever(getFolderTreeInfo.invoke(folderNode)) + .thenReturn(folderTreeInfo) + whenever(getContactItemFromInShareFolder.invoke(any(), any())).thenReturn(mock()) + } + underTest.setNode(folderNode.id.longValue) + underTest.uiState.test { + val actual = awaitItem() + Truth.assertThat(actual.folderTreeInfo).isEqualTo(folderTreeInfo) + Truth.assertThat(actual.isAvailableOfflineAvailable).isTrue() + Truth.assertThat(actual.sizeInBytes) + .isEqualTo(actualSize + versionsSize) + } + } + private fun mockMonitorStorageStateEvent(state: StorageState) { val storageStateEvent = StorageStateEvent( 1L, "", 1L, "", EventType.Storage, From cfd1cd561841311d7b911f2ace168dd9bd789643 Mon Sep 17 00:00:00 2001 From: Sougandh Mp Date: Thu, 18 May 2023 15:33:14 +0530 Subject: [PATCH 293/334] AND-16525: copy not working from MediaPlayerActivity --- .../privacy/android/app/mediaplayer/MediaPlayerActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/mediaplayer/MediaPlayerActivity.kt b/app/src/main/java/mega/privacy/android/app/mediaplayer/MediaPlayerActivity.kt index bbf356d2b2a..ba4030dd969 100644 --- a/app/src/main/java/mega/privacy/android/app/mediaplayer/MediaPlayerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/mediaplayer/MediaPlayerActivity.kt @@ -95,6 +95,7 @@ import mega.privacy.android.app.utils.Constants.INBOX_ADAPTER import mega.privacy.android.app.utils.Constants.INCOMING_SHARES_ADAPTER import mega.privacy.android.app.utils.Constants.INTENT_EXTRA_KEY_ADAPTER_TYPE import mega.privacy.android.app.utils.Constants.INTENT_EXTRA_KEY_CHAT_ID +import mega.privacy.android.app.utils.Constants.INTENT_EXTRA_KEY_COPY_TO import mega.privacy.android.app.utils.Constants.INTENT_EXTRA_KEY_FILE_NAME import mega.privacy.android.app.utils.Constants.INTENT_EXTRA_KEY_FIRST_LEVEL import mega.privacy.android.app.utils.Constants.INTENT_EXTRA_KEY_FROM @@ -1079,7 +1080,7 @@ abstract class MediaPlayerActivity : PasscodeActivity(), SnackbarShower, Activit REQUEST_CODE_SELECT_FOLDER_TO_COPY -> { val copyHandles = intent?.getLongArrayExtra(Constants.INTENT_EXTRA_KEY_COPY_HANDLES) ?: return - val toHandle = intent.getLongExtra(INTENT_EXTRA_KEY_MOVE_TO, INVALID_HANDLE) + val toHandle = intent.getLongExtra(INTENT_EXTRA_KEY_COPY_TO, INVALID_HANDLE) viewModel.copyNode( nodeHandle = copyHandles[0], From c4a073d07881b075bfb0858dfe301e8e16225a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yenel=20Rodr=C3=ADguez=20Hern=C3=A1ndez?= Date: Fri, 19 May 2023 00:48:05 +1200 Subject: [PATCH 294/334] T10249526 Integrate compose view in LoginFragment --- .../app/presentation/login/LoginFragment.kt | 3 +- .../app/presentation/login/LoginViewModel.kt | 36 ++++++++++++--- .../presentation/login/model/LoginState.kt | 3 ++ .../app/presentation/login/view/LoginView.kt | 14 ++++-- .../view/TwoFactorAuthenticationField.kt | 46 +++++++------------ .../presentation/login/view/LoginViewTest.kt | 3 +- .../view/TwoFactorAuthenticationFieldTest.kt | 4 +- 7 files changed, 65 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/login/LoginFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/login/LoginFragment.kt index 6074582967b..731f83a92bc 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/login/LoginFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/login/LoginFragment.kt @@ -145,7 +145,8 @@ class LoginFragment : Fragment() { onBackPressed = { onBackPressed(uiState) }, onUpdateKarereLogs = { viewModel.checkAndUpdateKarereLogs(requireActivity()) }, onUpdateSdkLogs = { viewModel.checkAndUpdateSDKLogs(requireActivity()) }, - onChangeApiServer = ::showChangeApiServerDialog + onChangeApiServer = ::showChangeApiServerDialog, + onFirstTime2FAConsumed = viewModel::onFirstTime2FAConsumed ) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/login/LoginViewModel.kt b/app/src/main/java/mega/privacy/android/app/presentation/login/LoginViewModel.kt index 3de909d31a9..b0163b645ba 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/login/LoginViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/login/LoginViewModel.kt @@ -227,14 +227,27 @@ class LoginViewModel @Inject constructor( fun stopLogin() { _state.update { it.copy( - pressedBackWhileLogin = true, - isAlreadyLoggedIn = false, fetchNodesUpdate = null, - is2FARequired = false, + isFirstTime = false, + isAlreadyLoggedIn = false, + pressedBackWhileLogin = true, is2FAEnabled = false, - isLoginInProgress = false, + is2FARequired = false, + twoFAPin = listOf("", "", "", "", "", ""), + multiFactorAuthState = null, + isAccountConfirmed = false, + rootNodesExists = false, + temporalEmail = null, + temporalPassword = null, + hasPreferences = false, + hasCUSetting = false, + isCUSettingEnabled = false, isLocalLogoutInProgress = true, isLoginRequired = true, + isLoginInProgress = false, + loginException = null, + ongoingTransfersExist = null, + isCheckingSignupLink = false ) } viewModelScope.launch { @@ -459,13 +472,15 @@ class LoginViewModel @Inject constructor( accountSession = state.value.accountSession?.copy(email = typedEmail) ?: AccountSession(email = typedEmail), password = typedPassword, - ongoingTransfersExist = null + ongoingTransfersExist = null, + pressedBackWhileLogin = false, ) } else { it.copy( isLoginInProgress = true, is2FARequired = false, - ongoingTransfersExist = null + ongoingTransfersExist = null, + pressedBackWhileLogin = false, ) } } @@ -491,7 +506,8 @@ class LoginViewModel @Inject constructor( isLoginInProgress = false, is2FAEnabled = true, isLoginRequired = false, - is2FARequired = true + is2FARequired = true, + isFirstTime2FA = triggered ) } } else { @@ -761,6 +777,12 @@ class LoginViewModel @Inject constructor( fun setSnackbarMessageId(@StringRes messageId: Int) = _state.update { state -> state.copy(snackbarMessage = triggered(messageId)) } + /** + * Sets isFirstTime2FA as consumed. + */ + fun onFirstTime2FAConsumed() = + _state.update { state -> state.copy(isFirstTime2FA = consumed) } + companion object { /** * Intent action for showing the login fetching nodes. diff --git a/app/src/main/java/mega/privacy/android/app/presentation/login/model/LoginState.kt b/app/src/main/java/mega/privacy/android/app/presentation/login/model/LoginState.kt index 449cb577345..d873bc43a5a 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/login/model/LoginState.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/login/model/LoginState.kt @@ -1,5 +1,6 @@ package mega.privacy.android.app.presentation.login.model +import de.palm.composestateevents.StateEvent import de.palm.composestateevents.StateEventWithContent import de.palm.composestateevents.consumed import mega.privacy.android.domain.entity.Feature @@ -22,6 +23,7 @@ import mega.privacy.android.domain.exception.LoginException * @property pressedBackWhileLogin True if pressed back while a login was in progress, false otherwise. * @property is2FAEnabled True if should ask for 2FA, false otherwise. * @property is2FARequired True if 2FA needs to be requested, false otherwise. + * @property isFirstTime2FA True if it is the first time the 2FA is requested. * @property twoFAPin Typed 2FA pin. * @property multiFactorAuthState [MultiFactorAuthState] * @property isAccountConfirmed True if account is confirmed after creation, false otherwise. @@ -55,6 +57,7 @@ data class LoginState( val pressedBackWhileLogin: Boolean = false, val is2FAEnabled: Boolean = false, val is2FARequired: Boolean = false, + val isFirstTime2FA: StateEvent = consumed, val twoFAPin: List = listOf("", "", "", "", "", ""), val multiFactorAuthState: MultiFactorAuthState? = null, val isAccountConfirmed: Boolean = false, diff --git a/app/src/main/java/mega/privacy/android/app/presentation/login/view/LoginView.kt b/app/src/main/java/mega/privacy/android/app/presentation/login/view/LoginView.kt index bb81b5c6e27..d91e1577225 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/login/view/LoginView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/login/view/LoginView.kt @@ -28,7 +28,6 @@ import androidx.compose.material.SnackbarHostState import androidx.compose.material.Text import androidx.compose.material.rememberScaffoldState import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -36,7 +35,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha -import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusManager import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusProperties @@ -95,6 +93,7 @@ import mega.privacy.android.domain.entity.login.FetchNodesUpdate * @param onUpdateKarereLogs Action when needs to update MegaChat logs. * @param onUpdateSdkLogs Action when needs to update Sdk logs. * @param onChangeApiServer Action when needs to update API server. + * @param onFirstTime2FAConsumed Action when the 2FA is shown for the first time. * @param modifier [Modifier] */ @Composable @@ -113,6 +112,7 @@ fun LoginView( onUpdateKarereLogs: () -> Unit, onUpdateSdkLogs: () -> Unit, onChangeApiServer: () -> Unit, + onFirstTime2FAConsumed: () -> Unit, modifier: Modifier = Modifier, ) { val scrollState = rememberScrollState() @@ -158,7 +158,8 @@ fun LoginView( paddingValues = paddingValues, on2FAPinChanged = on2FAPinChanged, on2FAChanged = on2FAChanged, - onLostAuthenticatorDevice = onLostAuthenticatorDevice + onLostAuthenticatorDevice = onLostAuthenticatorDevice, + onFirstTime2FAConsumed = onFirstTime2FAConsumed ) } } @@ -423,6 +424,7 @@ private fun TwoFactorAuthentication( on2FAPinChanged: (String, Int) -> Unit, on2FAChanged: (String) -> Unit, onLostAuthenticatorDevice: () -> Unit, + onFirstTime2FAConsumed: () -> Unit, modifier: Modifier = Modifier, ) = Box( modifier = modifier @@ -449,7 +451,8 @@ private fun TwoFactorAuthentication( isError = isError, on2FAPinChanged = on2FAPinChanged, on2FAChanged = on2FAChanged, - shouldRequestFocus = isChecking2FA + requestFocus = state.isFirstTime2FA, + onRequestFocusConsumed = onFirstTime2FAConsumed ) if (isError) { Text( @@ -528,7 +531,8 @@ private fun PreviewLoginView( onBackPressed = {}, onUpdateKarereLogs = {}, onUpdateSdkLogs = {}, - onChangeApiServer = {} + onChangeApiServer = {}, + onFirstTime2FAConsumed = {} ) } } diff --git a/app/src/main/java/mega/privacy/android/app/presentation/twofactorauthentication/view/TwoFactorAuthenticationField.kt b/app/src/main/java/mega/privacy/android/app/presentation/twofactorauthentication/view/TwoFactorAuthenticationField.kt index 333da5dd9b9..794e96d0b4a 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/twofactorauthentication/view/TwoFactorAuthenticationField.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/twofactorauthentication/view/TwoFactorAuthenticationField.kt @@ -46,6 +46,9 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import de.palm.composestateevents.EventEffect +import de.palm.composestateevents.StateEvent +import de.palm.composestateevents.consumed import mega.privacy.android.app.presentation.twofactorauthentication.extensions.getTwoFactorAuthentication import mega.privacy.android.app.presentation.twofactorauthentication.extensions.getUpdatedTwoFactorAuthentication import mega.privacy.android.core.ui.preview.CombinedThemePreviews @@ -56,12 +59,13 @@ import mega.privacy.android.core.ui.theme.extensions.grey_alpha_012_white_alpha_ /** * View for typing 2FA pin. * - * @param twoFAPin Typed 2FA pin. - * @param on2FAPinChanged Action when 2FA pin changes. - * @param on2FAChanged Action when the entire 2FA changes: Paste option. - * @param isError True if the 2FA pin was typed but is not the correct one. - * @param shouldRequestFocus True when it is required to request focus and open keyboard, false otherwise. - * @param modifier [Modifier]. + * @param twoFAPin Typed 2FA pin. + * @param on2FAPinChanged Action when 2FA pin changes. + * @param on2FAChanged Action when the entire 2FA changes: Paste option. + * @param isError True if the 2FA pin was typed but is not the correct one. + * @param requestFocus [StateEvent] + * @param onRequestFocusConsumed Action when request focus has been consumed. + * @param modifier [Modifier]. */ @OptIn(ExperimentalComposeUiApi::class) @Composable @@ -70,7 +74,8 @@ fun TwoFactorAuthenticationField( on2FAPinChanged: (String, Int) -> Unit, on2FAChanged: (String) -> Unit, isError: Boolean, - shouldRequestFocus: Boolean, + requestFocus: StateEvent, + onRequestFocusConsumed: () -> Unit, modifier: Modifier = Modifier, ) = Row(modifier = modifier.testTag(TWO_FACTOR_AUTHENTICATION_TEST_TAG)) { val ( @@ -175,26 +180,8 @@ fun TwoFactorAuthenticationField( ) } - if (shouldRequestFocus) { - LaunchedEffect(Unit) { - if (twoFAPin.none { it.isNotEmpty() }) { - focusRequesterSixthPin.requestFocus() - } else { - twoFAPin.forEachIndexed { index, pin -> - if (pin.isEmpty()) { - when (index) { - FIRST_PIN -> focusRequesterFirstPin.requestFocus() - SECOND_PIN -> focusRequesterSecondPin.requestFocus() - THIRD_PIN -> focusRequesterThirdPin.requestFocus() - FOURTH_PIN -> focusRequesterFourthPin.requestFocus() - FIFTH_PIN -> focusRequesterFifthPin.requestFocus() - SIXTH_PIN -> focusRequesterSixthPin.requestFocus() - } - return@forEachIndexed - } - } - } - } + EventEffect(event = requestFocus, onConsumed = onRequestFocusConsumed) { + focusRequesterFirstPin.requestFocus() } } @@ -241,7 +228,7 @@ private fun PinTwoFactorAuthentication( ), onValueChange = { with(it.text) { - if (isEmpty() || length == 1) onPinChanged(this) + if (pin != this && (isEmpty() || length == 1)) onPinChanged(this) else if (length == NUMBER_PINS) on2FAChanged(this) } }, @@ -312,7 +299,8 @@ private fun PreviewTwoFactorAuthenticationField() { } }, isError = isError, - shouldRequestFocus = true + requestFocus = consumed, + onRequestFocusConsumed = {} ) } } diff --git a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/login/view/LoginViewTest.kt b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/login/view/LoginViewTest.kt index 14d437f09e0..b359675c0af 100644 --- a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/login/view/LoginViewTest.kt +++ b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/login/view/LoginViewTest.kt @@ -48,7 +48,8 @@ class LoginViewTest { onBackPressed = {}, onUpdateKarereLogs = {}, onUpdateSdkLogs = {}, - onChangeApiServer = {} + onChangeApiServer = {}, + onFirstTime2FAConsumed = {} ) } } diff --git a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/twofactorauthentication/view/TwoFactorAuthenticationFieldTest.kt b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/twofactorauthentication/view/TwoFactorAuthenticationFieldTest.kt index e854f780c23..6ac9a0ce38a 100644 --- a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/twofactorauthentication/view/TwoFactorAuthenticationFieldTest.kt +++ b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/twofactorauthentication/view/TwoFactorAuthenticationFieldTest.kt @@ -4,6 +4,7 @@ import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.test.ext.junit.runners.AndroidJUnit4 +import de.palm.composestateevents.consumed import mega.privacy.android.app.presentation.twofactorauthentication.view.FIFTH_PIN_TEST_TAG import mega.privacy.android.app.presentation.twofactorauthentication.view.FIRST_PIN_TEST_TAG import mega.privacy.android.app.presentation.twofactorauthentication.view.FOURTH_PIN_TEST_TAG @@ -32,7 +33,8 @@ class TwoFactorAuthenticationFieldTest { on2FAPinChanged = { _, _ -> }, on2FAChanged = {}, isError = false, - shouldRequestFocus = false, + requestFocus = consumed, + onRequestFocusConsumed = {} ) } } From 0f59652a25308ff08c24f777cafa56821f4bc079 Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Thu, 18 May 2023 20:05:00 +0700 Subject: [PATCH 295/334] AND-16390,T10249587: Fix share file lost URI permission --- .../java/mega/privacy/android/app/main/FileExplorerActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/FileExplorerActivity.kt b/app/src/main/java/mega/privacy/android/app/main/FileExplorerActivity.kt index fa2573dc1b8..13f3136cee1 100644 --- a/app/src/main/java/mega/privacy/android/app/main/FileExplorerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/FileExplorerActivity.kt @@ -385,7 +385,6 @@ class FileExplorerActivity : TransfersManagementActivity(), MegaRequestListenerI Timber.d("onCreate first") super.onCreate(savedInstanceState) credentials = dbH.credentials - if (credentials != null && shouldRefreshSessionDueToSDK(true)) return onBackPressedDispatcher.addCallback(this, onBackPressedCallback) createChatLauncher = From 964443e5809a1a3fb473bc94e6af671d9247ae1f Mon Sep 17 00:00:00 2001 From: Veronika Koreiba Date: Mon, 22 May 2023 14:31:10 +1200 Subject: [PATCH 296/334] Update available translations --- app/src/main/res/values-ar/strings.xml | 69 ++++++++++++++++------ app/src/main/res/values-de/strings.xml | 55 ++++++++++++----- app/src/main/res/values-es/strings.xml | 60 ++++++++++++++----- app/src/main/res/values-fr/strings.xml | 56 +++++++++++++----- app/src/main/res/values-in/strings.xml | 48 ++++++++++----- app/src/main/res/values-it/strings.xml | 54 ++++++++++++----- app/src/main/res/values-ja/strings.xml | 46 ++++++++++----- app/src/main/res/values-ko/strings.xml | 52 ++++++++++------ app/src/main/res/values-nl/strings.xml | 51 +++++++++++----- app/src/main/res/values-pl/strings.xml | 63 +++++++++++++++----- app/src/main/res/values-pt/strings.xml | 56 +++++++++++++----- app/src/main/res/values-ro/strings.xml | 54 ++++++++++++----- app/src/main/res/values-ru/strings.xml | 59 +++++++++++++----- app/src/main/res/values-th/strings.xml | 48 ++++++++++----- app/src/main/res/values-vi/strings.xml | 51 +++++++++++----- app/src/main/res/values-zh-rCN/strings.xml | 46 ++++++++++----- app/src/main/res/values-zh-rTW/strings.xml | 68 +++++++++++++-------- app/src/main/res/values/strings.xml | 51 +++++++++++----- 18 files changed, 710 insertions(+), 277 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index acabc2e41de..72003cf5d9a 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -5254,20 +5254,6 @@ خامس أحد من كل %1$d شهراً اعتباراً من تاريخ [A]%2$s[/A] من [B]%3$s إلى %4$s[/B] خامس أحد من كل %1$d شهر اعتباراً من تاريخ [A]%2$s[/A] من [B]%3$s إلى %4$s[/B] - - الاثنين - - الثلاثاء - - الأربعاء - - الخميس - - الجمعة - - السبت - - الأحد اليوم، %1$s من %2$s إلى %3$s @@ -5528,9 +5514,9 @@ اسم الاجتماع مطلوب - Enter up to 30 characters + أدخل ما يصل إلى 30  محرفًا - Enter up to 3,000 characters + أدخل ما يصل إلى 3000  محرفًا تم تحديد موعد الاجتماع @@ -5618,7 +5604,7 @@ تعذرت إزالة %1$d عنصر نهائيًا - Enter time + أدخل الوقت دعوات الاجتماع @@ -5631,4 +5617,53 @@ يبدأ الاجتماع خلال 15 دقيقة يبدأ الاجتماع الآن + + Share the decryption key too? + + The decryption key has been exported and will be shared with the link. + + Share link only + + Share link and key + + + شارك الرابط + شارك الرابط + شارك الروابط + شارك الروابط + شارك الروابط + شارك الروابط + + + + Link copied to clipboard + Links copied to clipboard + + + + إزالة الرابط؟ + إزالة الرابط؟ + إزالة الروابط؟ + إزالة الروابط؟ + إزالة الروابط؟ + إزالة الروابط؟ + + + + People you shared this link with, will lose access to the album. + People you shared these links with, will lose access to the albums. + + + + إزالة الرابط + إزالة الرابط + إزالة الروابط + إزالة الروابط + إزالة الروابط + إزالة الروابط + + + أدخل مفتاح فك التشفير + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index ea69d1bba11..789b69dbe2c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -4514,20 +4514,6 @@ Ab [A]%2$s[/A] am fünften Sonntag des Monats von [B]%3$s bis %4$s[/B] Ab [A]%2$s[/A] am fünften Sonntag jedes %1$d. Monats von [B]%3$s bis %4$s[/B] - - Mo - - Di - - Mi - - Do - - Fr - - Sa - - So Heute, den %1$s von %2$s bis %3$s @@ -4720,9 +4706,9 @@ Der Meetingname ist erforderlich - Enter up to 30 characters + Maximal 30 Zeichen eingeben - Enter up to 3,000 characters + Maximal 3.000 Zeichen eingeben Meeting geplant @@ -4791,4 +4777,41 @@ Meeting beginnt in 15 Minuten Meeting beginnt jetzt + + Schlüssel für die Entschlüsselung ebenfalls teilen? + + Der Schlüssel für die Entschlüsselung wurde exportiert und wird mit dem Link geteilt. + + Nur Link teilen + + Link und Schlüssel teilen + + + Link teilen + Links teilen + + + + Link in Zwischenablage kopiert + Links in Zwischenablage kopiert + + + + Link entfernen? + Links entfernen? + + + + Die Personen, mit denen Sie diesen Link geteilt haben, können nicht mehr auf das Album zugreifen. + Die Personen, mit denen Sie diese Links geteilt haben, können nicht mehr auf die Alben zugreifen. + + + + Link entfernen + Links entfernen + + + Schlüssel eingeben + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index c47606fc782..fcc73362142 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -4699,20 +4699,6 @@ El quinto domingo de cada %1$d meses desde el [A]%2$s[/A] de [B]%3$s a %4$s[/B] El quinto domingo de cada %1$d meses desde el [A]%2$s[/A] de [B]%3$s a %4$s[/B] - - Lun - - Mar - - Mie - - Jue - - Vie - - Sab - - Dom Hoy, %1$s de %2$s a %3$s @@ -4922,9 +4908,9 @@ Es necesario indicar el nombre de la reunión - Enter up to 30 characters + Introducir hasta 30 caracteres - Enter up to 3,000 characters + Introducir hasta 3000 caracteres Reunión programada @@ -5001,4 +4987,46 @@ La reunión empieza en 15 minutos La reunión empieza ahora + + ¿Compartir también la clave de descifrado? + + La clave de descifrado se ha exportado y se compartirá con el enlace. + + Compartir solo el enlace + + Compartir enlace y clave + + + Compartir enlace + Compartir enlaces + Compartir enlaces + + + + Enlace copiado al portapapeles + Enlaces copiados al portapapeles + Enlaces copiados al portapapeles + + + + ¿Eliminar enlace? + ¿Eliminar enlaces? + ¿Eliminar enlaces? + + + + Las personas con quien hayas compartido este enlace perderán acceso al álbum. + Las personas con quien hayas compartido estos enlaces perderán acceso a los álbumes. + Las personas con quien hayas compartido estos enlaces perderán acceso a los álbumes. + + + + Eliminar enlace + Eliminar enlaces + Eliminar enlaces + + + Introducir la clave de descifrado + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 399ca0e8c33..91724648ce9 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -4699,20 +4699,6 @@ Cinquième dimanche tous les %1$d mois à partir du [A]%2$s[/A] de [B]%3$s à %4$s[/B] Cinquième dimanche tous les %1$d mois à partir du [A]%2$s[/A] de [B]%3$s à %4$s[/B] - - Lundi - - Mardi - - Mercredi - - Jeudi - - Vendredi - - Samedi - - Dimanche Aujourd’hui %1$s de %2$s à %3$s @@ -5001,4 +4987,46 @@ La réunion commence dans 15 minutes La réunion commence maintenant + + Partager aussi la clé de déchiffrement ? + + La clé de déchiffrement a été exportée et sera partagée avec le lien. + + Ne partager que le lien + + Partager le lien et la clé + + + Partager un lien + Partager des liens + Partager des liens + + + + Le lien a été copié dans le presse-papiers + Les liens ont été copiés dans le presse-papiers + Les liens ont été copiés dans le presse-papiers + + + + Supprimer le lien ? + Supprimer les liens ? + Supprimer les liens ? + + + + Les personnes avec qui vous avez partagé ce lien perdront l’accès à l’album. + Les personnes avec qui vous avez partagé ces liens perdront l’accès aux albums. + Les personnes avec qui vous avez partagé ces liens perdront l’accès aux albums. + + + + Supprimer le lien + Supprimer les liens + Supprimer les liens + + + Saisir la clé de déchiffrement + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index b64af01ae60..3f3d8000468 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -4329,20 +4329,6 @@ Minggu kelima dari setiap %1$d bulan efektif [A]%2$s[/A] dari [B]%3$s ke %4$s[/B] - - Sen - - Sel - - Rabu - - Kam - - Jum - - Sab - - Min Hari ini, %1$s dari %2$s ke %3$s @@ -4581,4 +4567,38 @@ Rapat dimulai dalam 15 menit Rapat dimulai sekarang + + Share the decryption key too? + + The decryption key has been exported and will be shared with the link. + + Share link only + + Share link and key + + + Bagikan link + + + + Link copied to clipboard + Links copied to clipboard + + + + Hapus tautan? + + + + People you shared this link with, will lose access to the album. + People you shared these links with, will lose access to the albums. + + + + Hapus tautan + + + Masukkan kunci dekripsi + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 1303349903f..b0b814a0703 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -4699,20 +4699,6 @@ La quinta domenica di ogni %1$d di mesi a partire dal [A]%2$s[/A] dalle [B]%3$s alle %4$s[/B] La quinta domenica di ogni %1$d mesi a partire dal [A]%2$s[/A] dalle [B]%3$s alle %4$s[/B] - - Lun - - Mar - - Mer - - Gio - - Ven - - Sab - - Dom Oggi, %1$s dalle %2$s alle %3$s @@ -5001,4 +4987,44 @@ Il meeting inizierà tra 15 minuti Il meeting inizia ora + + Share the decryption key too? + + The decryption key has been exported and will be shared with the link. + + Share link only + + Share link and key + + + Condividi link + Condividi link + Condividi link + + + + Link copied to clipboard + Links copied to clipboard + + + + Rimuovere il link? + Rimuovere i link? + Rimuovere i link? + + + + People you shared this link with, will lose access to the album. + People you shared these links with, will lose access to the albums. + + + + Rimuovi il link + Rimuovi i link + Rimuovi i link + + + Inserisci la chiave di decrittazione + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 72fa7afad6b..db1f8c96d9f 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -4329,20 +4329,6 @@ [A]%2$s[/A]から、%1$dか月ごとに第五日曜日に、[B]%3$sから%4$s[/B] - - - - - - - - - - - - - - 今日、%1$s%2$sから%3$s @@ -4581,4 +4567,36 @@ ミーティングが15分後に開始します ミーティングが今から始まります + + 復号化キーも共有しますか? + + 復号化キーはエクスポートされており、リンクと共有されます。 + + リンクのみを共有 + + リンクとキーを共有 + + + リンクの共有 + + + + リンクがクリップボードにコピーされました + + + + リンクを削除しますか? + + + + これらのリンクを共有した人はアルバムにアクセスできなくなります。 + + + + リンクを削除 + + + 復号キーを入力してください + + アルバムにアクセスするには、復号化キーを入力します。お持ちでない場合は、リンクを共有した人に連絡してください。 \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 6792d240d71..be85d192c8b 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -4329,20 +4329,6 @@ [A]%2$s부터[/A] 매 %1$d개월마다 다섯번째 일요일 [B]%3$s부터 %4$s[/B]까지 - - - - - - - - - - - - - - 오늘, %1$s %2$s부터 %3$s까지 @@ -4518,9 +4504,9 @@ 회의 이름은 필수입니다 - Enter up to 30 characters + 30자 이하로 입력하세요  - Enter up to 3,000 characters + 3,000자 이하로 입력하세요  회의가 예약되었습니다 @@ -4568,7 +4554,7 @@ %1$d개의 항목을 영구적으로 삭제하지 못 했습니다 - Enter time + 시간 입력 회의 초대 @@ -4581,4 +4567,36 @@ 15분 내에 회의가 시작됩니다 회의가 지금 시작됩니다 + + 복호화 키도 공유할까요? + + 복호화 키가 내보내졌으며 링크와 함께 공유될 것입니다. + + 링크만 공유 + + 링크와 키 공유 + + + 링크 공유 + + + + 링크가 클립보드에 복사되었습니다 + + + + 링크를 제거할까요? + + + + 이 링크를 가진 사람들이 사진첩에 접근하지 못 하게 됩니다. + + + + 링크 제거 + + + 복호화 키를 입력하세요 + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 557987f52a9..3427f0f6cc3 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -4514,20 +4514,6 @@ De vijfde Zondag van elke maand van kracht [A]%2$s[/A] van [B]%3$s tot %4$s[/B] De vijfde Zondag van elke %1$d maanden van kracht [A]%2$s[/A] van [B]%3$s tot %4$s[/B] - - Ma - - Di - - Woe - - Do - - Vrij - - Za - - Zo Vandaag, %1$s van %2$s tot %3$s @@ -4791,4 +4777,41 @@ Vergadering begint over 15 minuten Vergadering begint nu + + De decoderingssleutel ook delen? + + De decoderingssleutel is geëxporteerd en wordt gedeeld met de link. + + Alleen link delen + + Link en sleutel delen + + + Link delen + Links delen + + + + Koppeling gekopieerd naar klembord + Koppelingen gekopieerd naar klembord + + + + Link verwijderen? + Links verwijderen? + + + + Mensen met wie u deze link hebt gedeeld, verliezen de toegang tot de albums. + Mensen met wie u deze links hebt gedeeld, verliezen de toegang tot de albums. + + + + Link verwijderen + Links verwijderen + + + Vul decoderingssleutel in + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 9dc6d73bdb6..6da20833ed5 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -4884,20 +4884,6 @@ Piąta niedziela każdego %1$d miesiąca zaczynając [A]%2$s[/A] od [B]%3$s do %4$s[/B] Piąta niedziela każdego %1$d miesiąca zaczynając [A]%2$s[/A] od [B]%3$s do %4$s[/B] - - Pon - - Wto - - Nie - - Czw - - Pią - - Sob - - Nie Dzisiaj, %1$s od %2$s do %3$s @@ -5124,9 +5110,9 @@ Nazwa spotkania jest wymagana - Enter up to 30 characters + Wprowadź do 30 znaków - Enter up to 3,000 characters + Wprowadź do 3000  znaków Spotkanie zostało zaplanowane @@ -5211,4 +5197,49 @@ Spotkanie rozpoczyna się za 15 minut Spotkanie zaczyna się teraz + + Share the decryption key too? + + The decryption key has been exported and will be shared with the link. + + Share link only + + Share link and key + + + Udostępniony link + Udostępnione linki + Udostępnione linki + Udostępnione linki + + + + Link skopiowany do schowka + Linki skopiowane do schowka + Linki skopiowane do schowka + Linki skopiowane do schowka + + + + Usunąć link? + Usunąć linki? + Usunąć linki? + Usunąć linki? + + + + People you shared this link with, will lose access to the album. + People you shared these links with, will lose access to the albums. + + + + Usuń link + Usuń linki + Usuń linki + Usuń linki + + + Wprowadź klucz deszyfrujący + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 831370b2b97..78efcd0fb9b 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -4699,20 +4699,6 @@ Quinto domingo de cada %1$d meses , a partir de [A]%2$s[/A], das [B]%3$s às %4$s[/B] Quinto domingo de cada %1$d meses , a partir de [A]%2$s[/A], das [B]%3$s às %4$s[/B] - - Seg - - Ter - - Qua - - Qui - - Sex - - Sáb - - Dom Hoje, %1$s, das %2$s às %3$s @@ -5001,4 +4987,46 @@ A reunião começa em 15 minutos. A reunião está começando + + Também compartilhar a chave de decodificação? + + A chave de decodificação foi exportada e será compartilhada junto com o link. + + Compartilhar somente o link + + Compartilhar o link e a chave + + + Compartilhar link + Compartilhar links + Compartilhar links + + + + Link copiado para a área de transferência + Links copiados para a área de transferência + Links copiados para a área de transferência + + + + Remover o link? + Remover os links? + Remover os links? + + + + As pessoas com as quais você compartilhou esse link perderão o acesso ao álbum. + As pessoas com as quais você compartilhou esses links perderão o acesso aos álbuns. + As pessoas com as quais você compartilhou esses links perderão o acesso aos álbuns. + + + + Remover link + Remover links + Remover links + + + Digite a chave de decodificação + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index e0b78ba263f..5f9cb53d943 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -4687,20 +4687,6 @@ A cincea duminică la fiecare %1$d luni, începând cu [A]%2$s[/A] de la [B]%3$s la %4$s[/B] A cincea duminică la fiecare %1$d de luni, începând cu [A]%2$s[/A] de la [B]%3$s la %4$s[/B] - - Lun - - Mar - - Mie - - Joi - - Vin - - Sâm - - Dum Azi, %1$s de la %2$s la %3$s @@ -4966,4 +4952,44 @@ Meeting starts in 15 minutes Meeting starts now + + Share the decryption key too? + + The decryption key has been exported and will be shared with the link. + + Share link only + + Share link and key + + + Partajează linkul + Partajează linkurile + Partajează linkurile + + + + Link copied to clipboard + Links copied to clipboard + + + + Elimini linkul? + Elimini linkurile? + Elimini linkurile? + + + + People you shared this link with, will lose access to the album. + People you shared these links with, will lose access to the albums. + + + + Elimină linkul + Elimină linkurile + Elimină linkurile + + + Introdu cheia de decriptare + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a6e8945fa4c..8b3e7f081c6 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -4884,20 +4884,6 @@ Каждые %1$d месяцев в пятое воскресенье с [A]%2$s[/A] с [B]%3$s до %4$s[/B] Каждые %1$d месяца в пятое воскресенье с [A]%2$s[/A] с [B]%3$s до %4$s[/B] - - Пн - - Вт - - Ср - - Чт - - Пт - - Сб - - Вс Сегодня, %1$s, с %2$s до %3$s @@ -5211,4 +5197,49 @@ Встреча начнётся через 15 минут Встреча начинается + + Share the decryption key too? + + The decryption key has been exported and will be shared with the link. + + Share link only + + Share link and key + + + Поделиться ссылкой + Поделиться ссылками + Поделиться ссылками + Поделиться ссылками + + + + Ссылка скопирована в буфер обмена + Ссылки скопированы в буфер обмена + Ссылки скопированы в буфер обмена + Ссылки скопированы в буфер обмена + + + + Удалить ссылку? + Удалить ссылки? + Удалить ссылки? + Удалить ссылки? + + + + People you shared this link with, will lose access to the album. + People you shared these links with, will lose access to the albums. + + + + Удалить ссылку + Удалить ссылки + Удалить ссылки + Удалить ссылки + + + Введите ключ дешифрирования + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 1deb010fc1e..910b6592b32 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -4329,20 +4329,6 @@ อาทิตย์สุดท้าย ของทุก %1$d เดือน มีผล [A]%2$s[/A] ตั้งแต่ [B]%3$s จนถึง %4$s[/B] - - จ. - - อ. - - พ. - - พฤ. - - ศ. - - ส. - - อา. วันนี้ %1$s ตั้งแต่ %2$s จนถึง %3$s @@ -4581,4 +4567,38 @@ การประชุมจะเริ่มในอีก 15 นาที การประชุมเริ่มขึ้นแล้ว + + Share the decryption key too? + + The decryption key has been exported and will be shared with the link. + + Share link only + + Share link and key + + + แชร์ลิงก์ + + + + Link copied to clipboard + Links copied to clipboard + + + + ลบลิงก์หรือไม่ + + + + People you shared this link with, will lose access to the album. + People you shared these links with, will lose access to the albums. + + + + เอาลิงก์ออก + + + กรอกคีย์เพื่อถอดรหัสลับ + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 570cbeffd71..911bce2d9d4 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -4329,20 +4329,6 @@ Chủ Nhật lần thứ năm của mỗi %1$d tháng, có hiệu lực từ [A]%2$s[/A] từ [B]%3$s đến %4$s[/B] - - Thứ2 - - Thứ3 - - Thứ4 - - Thứ5 - - Thứ6 - - Thứ7 - - CN Hôm nay, %1$s từ %2$s đến %3$s @@ -4518,9 +4504,9 @@ Tên cho cuộc họp là bắt buộc - Enter up to 30 characters + Nhập được tới 30 ký tự - Enter up to 3,000 characters + Nhập được tới 3.000 ký tự Cuộc họp được lên lịch @@ -4581,4 +4567,37 @@ Cuộc họp bắt đầu trong 15 phút Cuộc họp bắt đầu ngay bây giờ + + Share the decryption key too? + + The decryption key has been exported and will be shared with the link. + + Share link only + + Share link and key + + + Chia sẻ đường liên kết + + + + Các đường liên kết đã được ghi vào bảng nhớ tạm + + + + Loại bỏ các đường liên kết? + + + + People you shared this link with, will lose access to the album. + People you shared these links with, will lose access to the albums. + + + + Loại bỏ đường liên kết + + + Nhập chìa khóa giải mã + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 2f68bb88fd3..a2ee0119a5b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -4329,20 +4329,6 @@ 从[A]%2$s[/A]起,每%1$d个月第五个星期日的[B]%3$s到%4$s[/B] - - - - - - - - - - 星期五 - - - - 今天,%1$s从%2$s到%3$s @@ -4581,4 +4567,36 @@ 会议将在15分钟后开始 会议现在开始 + + 同时共享解密密钥? + + 解密密钥已导出并将与链接一起共享。 + + 仅共享链接 + + 共享链接和密钥 + + + 分享链接 + + + + 已复制链接到剪贴板 + + + + 移除链接? + + + + 您与之共享这些链接的人,将失去对相册的访问权限。 + + + + 移除链接 + + + 请输入解密密钥 + + 要访问该相册,请输入解密密钥。如果您没有,请联系与您共享链接的人。 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 8f39ffa1ff5..15130373b5a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -241,7 +241,7 @@ 選擇帳戶 - 可離線存取 + 離線使用 大小 @@ -359,7 +359,7 @@ 移除 - 從離線存取中移除 + 從離線使用中移除 共享資料夾 @@ -1595,7 +1595,7 @@ [B]沒有檔案在您的[/B][A]雲端硬碟[/A] - [A]離線存取[/A]裡[B]沒有檔案[/B] + [A]離線使用[/A]裡[B]沒有檔案[/B] [B]沒有[/B][A]聯絡人[/A] @@ -1896,7 +1896,7 @@ 已停用雙重驗證 - 於…開啟 + 從…開啟 在您的裝置上,沒有任何可用的應用程式可以用來啟用雙重驗證 @@ -1922,7 +1922,7 @@ 沒有網路連線。請重新連線來開啟檔案。 - 檔案已存在於離線存取裡 + 檔案已存在於離線使用中 訊息未轉發 @@ -2678,7 +2678,7 @@ 請聯繫您的商業帳戶管理員來解決問題並啟用您的帳戶。 - 當您登出時,裝置中可離線存取的檔案將會被刪除。 + 當您登出時,裝置中可離線使用的檔案將會被刪除。 當您登出時,正在進行中的傳輸將取消。 @@ -3376,9 +3376,9 @@ 已發送邀請。查看已發送請求。 - 檔案可離線存取 + 檔案可離線使用 - 檔案已從離線存取移除 + 檔案已從離線使用中移除 打開連結 @@ -3804,7 +3804,7 @@ 稍後再說 - 開啟通知 + 取得通知 MEGA將會向您發送通話、聊天和檔案傳輸的通知。 @@ -4329,20 +4329,6 @@ 從[A]%2$s[/A]起,每%1$d個月第五個星期天的[B]%3$s到%4$s[/B] - - 星期一 - - 星期二 - - 星期三 - - 星期四 - - 星期五 - - 星期六 - - 星期日 今天,%1$s從%2$s到%3$s @@ -4518,9 +4504,9 @@ 會議名稱為必填 - Enter up to 30 characters + 最多輸入30 個字元(15個中文字) - Enter up to 3,000 characters + 最多輸入3,000 個字元(1,500個中文字) 已安排會議 @@ -4581,4 +4567,36 @@ 會議將在15分鐘後開始 會議現在開始 + + 同時共享解密金鑰? + + 解密金鑰已匯出並將與連結一起共享。 + + 僅共享連結 + + 共享連結和金鑰 + + + 共享連結 + + + + 連結已複製到剪貼簿 + + + + 移除連結? + + + + 您與之共享這些連結的人將失去這些相簿的存取權限。 + + + + 移除連結 + + + 請輸入解密金鑰 + + 要存取相簿,請輸入解密金鑰。如果您沒有金鑰,請聯繫共享金鑰給您的人。 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0103cfb3ed0..2b3271c7092 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -4514,20 +4514,6 @@ Fifth Sunday of every month effective [A]%2$s[/A] from [B]%3$s to %4$s[/B] Fifth Sunday of every %1$d months effective [A]%2$s[/A] from [B]%3$s to %4$s[/B] - - Mon - - Tue - - Wed - - Thu - - Fri - - Sat - - Sun Today, %1$s from %2$s to %3$s @@ -4791,4 +4777,41 @@ Meeting starts in 15 minutes Meeting starts now + + Share the decryption key too? + + The decryption key has been exported and will be shared with the link. + + Share link only + + Share link and key + + + Share link + Share links + + + + Link copied to clipboard + Links copied to clipboard + + + + Remove link? + Remove links? + + + + People you shared this link with, will lose access to the album. + People you shared these links with, will lose access to the albums. + + + + Remove link + Remove links + + + Enter decryption key + + To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. \ No newline at end of file From d4f967fdd817698bf5af27c1dc996fdecd771282 Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Mon, 22 May 2023 14:38:19 +0700 Subject: [PATCH 297/334] AND-16537: Fix Search results are reset when user tries to sort the results (cherry picked from commit ab7ef6a98bfbd31bf8b18828b553a35a28f894dc) --- .../android/app/presentation/search/SearchFragment.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt index f21a5af8012..681d1b9d533 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/search/SearchFragment.kt @@ -202,7 +202,7 @@ class SearchFragment : RotatableFragment() { sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver { hideMultipleSelect() - refresh() + refresh(isInvalidateOptionMenu = false) }) searchViewModel.updateNodes.observe( @@ -769,10 +769,12 @@ class SearchFragment : RotatableFragment() { /** * This function refresh the search */ - fun refresh() { + fun refresh(isInvalidateOptionMenu: Boolean = true) { Timber.d("refresh ") newSearchNodesTask() - managerActivity.invalidateOptionsMenu() + if (isInvalidateOptionMenu) { + managerActivity.invalidateOptionsMenu() + } visibilityFastScroller() } From de1822efa3830fc0cf6c0f6c44896fcacdb5eb22 Mon Sep 17 00:00:00 2001 From: Pau Dominkovics Date: Mon, 22 May 2023 09:28:52 +0200 Subject: [PATCH 298/334] AND-16546: Add test tags to owner status --- .../presentation/fileinfo/view/FileInfoViewConstants.kt | 3 +++ .../app/presentation/fileinfo/view/OwnerInfoView.kt | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoViewConstants.kt b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoViewConstants.kt index 9b906a5d607..d441bca988d 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoViewConstants.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/FileInfoViewConstants.kt @@ -14,6 +14,9 @@ internal const val TEST_TAKE_DOWN_CLOSE = "takeDownClose" internal const val TEST_TAKE_TEXT = "takeDownText" internal const val TEST_TAG_CONTACT_ITEM_SHARED = "contactItem" internal const val TEST_TAG_CONTACT_ITEM_SHARED_DOTS = "contactItemDots" +internal const val TEST_TAG_OWNER_STATUS = "ownerStatus" +internal const val TEST_TAG_OWNER_NAME = "ownerName" +internal const val TEST_TAG_OWNER_EMAIL = "ownerEmail" internal const val TEST_TAG_SHARES_HEADER = "shareContactsHeader" internal const val TEST_TAG_SHOW_MORE = "shareMoreButton" internal const val MENU_ACTIONS_TO_SHOW = 2 \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/OwnerInfoView.kt b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/OwnerInfoView.kt index defb26d0e65..331493c11c9 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/OwnerInfoView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/fileinfo/view/OwnerInfoView.kt @@ -11,6 +11,7 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.SpanStyle @@ -29,7 +30,7 @@ import mega.privacy.android.core.ui.theme.extensions.textColorSecondary import mega.privacy.android.domain.entity.contacts.ContactItem /** - * View to show Node owner information + * View to show Node's owner information * @param contactItem of the owner * @param modifier */ @@ -57,16 +58,19 @@ internal fun OwnerInfoView( } } Text( + modifier = Modifier.testTag(TEST_TAG_OWNER_NAME), text = text, maxLines = 1, style = MaterialTheme.typography.caption.copy(color = MaterialTheme.colors.textColorPrimary), ) Image( + modifier = Modifier.testTag(TEST_TAG_OWNER_STATUS), painter = painterResource(id = contactItem.status.iconRes(MaterialTheme.colors.isLight)), - contentDescription = "Access permission" + contentDescription = "Contact status" ) } Text( + modifier = Modifier.testTag(TEST_TAG_OWNER_EMAIL), text = contactItem.email, maxLines = 1, style = MaterialTheme.typography.subtitle1.copy(color = MaterialTheme.colors.grey_alpha_038_white_alpha_038), From abf6502c28307b21ec89852ba0d5c5d5debc8200 Mon Sep 17 00:00:00 2001 From: Yenel Date: Mon, 22 May 2023 13:39:21 +0200 Subject: [PATCH 299/334] CC-4449 AND - App crashes when enable selection mode then MD view, tap back from selection mode --- .../android/app/presentation/clouddrive/FileBrowserFragment.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt index 9d4cf6d75bc..80b4f1fa8fc 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/clouddrive/FileBrowserFragment.kt @@ -1198,6 +1198,8 @@ class FileBrowserFragment : RotatableFragment() { * Show Media discovery and launch [MediaDiscoveryFragment] */ private fun showMediaDiscovery(isOpenByMDIcon: Boolean = false) { + clearSelections() + hideMultipleSelect() requireActivity().lifecycleScope.launch { (requireActivity() as? ManagerActivity)?.skipToMediaDiscoveryFragment( fragment = MediaDiscoveryFragment.getNewInstance( From d529e345bd14920a699723dfa5ebc5d1c1854436 Mon Sep 17 00:00:00 2001 From: Sougandh Mp Date: Mon, 22 May 2023 18:51:30 +0530 Subject: [PATCH 300/334] AND-16553: Copy not working from Text viewer --- .../mega/privacy/android/app/textEditor/TextEditorActivity.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/textEditor/TextEditorActivity.kt b/app/src/main/java/mega/privacy/android/app/textEditor/TextEditorActivity.kt index cae84553ef3..3c773e48151 100644 --- a/app/src/main/java/mega/privacy/android/app/textEditor/TextEditorActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/textEditor/TextEditorActivity.kt @@ -53,6 +53,7 @@ import mega.privacy.android.app.utils.Constants.FILE_LINK_ADAPTER import mega.privacy.android.app.utils.Constants.FOLDER_LINK_ADAPTER import mega.privacy.android.app.utils.Constants.FROM_CHAT import mega.privacy.android.app.utils.Constants.FROM_HOME_PAGE +import mega.privacy.android.app.utils.Constants.INTENT_EXTRA_KEY_COPY_TO import mega.privacy.android.app.utils.Constants.INTENT_EXTRA_KEY_IMPORT_TO import mega.privacy.android.app.utils.Constants.INTENT_EXTRA_KEY_MOVE_TO import mega.privacy.android.app.utils.Constants.INVALID_VALUE @@ -330,7 +331,7 @@ class TextEditorActivity : PasscodeActivity(), SnackbarShower, Scrollable { viewModel.moveNode(toHandle, this) } REQUEST_CODE_SELECT_FOLDER_TO_COPY -> { - val toHandle = intent?.getLongExtra(INTENT_EXTRA_KEY_MOVE_TO, INVALID_HANDLE) + val toHandle = intent?.getLongExtra(INTENT_EXTRA_KEY_COPY_TO, INVALID_HANDLE) ?: return viewModel.copyNode(toHandle, this) From 620c12cd1e63a7a28b41fa58a253d0a590e6eabb Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Tue, 23 May 2023 07:18:29 +0700 Subject: [PATCH 301/334] FM-320: The header for sorting does not show correctly after restart the app (cherry picked from commit 206bb82785c32fe65295fa17949d14a8dea54598) --- .../android/app/fragments/homepage/SortByHeaderViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/homepage/SortByHeaderViewModel.kt b/app/src/main/java/mega/privacy/android/app/fragments/homepage/SortByHeaderViewModel.kt index aba2f93adc1..2d83a0bb57a 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/homepage/SortByHeaderViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/homepage/SortByHeaderViewModel.kt @@ -146,6 +146,7 @@ class SortByHeaderViewModel @Inject constructor( _cloudSortOrder.value = getCloudSortOrder() _othersSortOrder.value = getOthersSortOrder() _offlineSortOrder.value = getOfflineSortOrder() + order = Triple(_cloudSortOrder.value, _othersSortOrder.value, _offlineSortOrder.value) setOldOrder() monitorViewType().collect { viewType -> From ef9ca35d817df123cc880d03d8410583240e639f Mon Sep 17 00:00:00 2001 From: Veronika Koreiba Date: Thu, 25 May 2023 14:53:45 +1200 Subject: [PATCH 302/334] Update SDK and MEGAchat submodules --- build.gradle | 2 +- sdk/src/main/jni/mega/sdk | 2 +- sdk/src/main/jni/megachat/sdk | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 8769ef136c2..cad36093de1 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230516.213653-rel' + megaSdkVersion = '20230525.012323-rel' //JDK and Java Version jdk = "17" diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index 94e2b9dd1db..2eca245a15c 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit 94e2b9dd1db6a886e21cc1ee826bda58c8c33f99 +Subproject commit 2eca245a15c253ca1deaa925243e4093c87501f7 diff --git a/sdk/src/main/jni/megachat/sdk b/sdk/src/main/jni/megachat/sdk index b84c9e70f4c..25df51bd3ad 160000 --- a/sdk/src/main/jni/megachat/sdk +++ b/sdk/src/main/jni/megachat/sdk @@ -1 +1 @@ -Subproject commit b84c9e70f4cfff1410b04ef147927c261624d738 +Subproject commit 25df51bd3add2e2e8475cec3c0ebc96e6a0cc09d From e7c5474f7d638193404abea5e5546af71e79701c Mon Sep 17 00:00:00 2001 From: Yenel Date: Thu, 25 May 2023 10:10:23 +0200 Subject: [PATCH 303/334] Update SDK and MEGAchat submodules --- sdk/src/main/jni/mega/sdk | 2 +- sdk/src/main/jni/megachat/sdk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index 2eca245a15c..e213ad85818 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit 2eca245a15c253ca1deaa925243e4093c87501f7 +Subproject commit e213ad85818a50836ae342f9c1656028646e0d34 diff --git a/sdk/src/main/jni/megachat/sdk b/sdk/src/main/jni/megachat/sdk index 25df51bd3ad..b7e3987f97e 160000 --- a/sdk/src/main/jni/megachat/sdk +++ b/sdk/src/main/jni/megachat/sdk @@ -1 +1 @@ -Subproject commit 25df51bd3add2e2e8475cec3c0ebc96e6a0cc09d +Subproject commit b7e3987f97e1bb648f4b04fc3572d48285984c1b From 0371a956c091740819431f67521ad181951428fe Mon Sep 17 00:00:00 2001 From: Veronika Koreiba Date: Fri, 26 May 2023 10:30:47 +1200 Subject: [PATCH 304/334] Update SDK and MEGAchat submodule --- build.gradle | 2 +- sdk/src/main/jni/mega/sdk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index cad36093de1..cb3bc3d89de 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230525.012323-rel' + megaSdkVersion = '20230525.211514-rel' //JDK and Java Version jdk = "17" diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index e213ad85818..a46a9d45fac 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit e213ad85818a50836ae342f9c1656028646e0d34 +Subproject commit a46a9d45fac09f8e95a63fa8e0d53b1b1ab22b9f From 291960751861188342a1901c5bade1b79bc48dd6 Mon Sep 17 00:00:00 2001 From: Bhavna Thacker Date: Tue, 23 May 2023 11:54:30 +0530 Subject: [PATCH 305/334] AND-16566: Remove AlarmReceiver from Manifest --- app/src/main/AndroidManifest.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e1886927423..2880f18f22e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -661,13 +661,6 @@ android:name="mega.privacy.android.app.mediaplayer.SelectSubtitleFileActivity" android:launchMode="singleTop" /> - - - - - - Date: Mon, 29 May 2023 13:24:13 +1200 Subject: [PATCH 306/334] Pre-release v8.2: Update available translations --- app/src/main/res/values-ar/strings.xml | 48 ++++++++++++++---- app/src/main/res/values-de/strings.xml | 24 ++++++++- app/src/main/res/values-es/strings.xml | 22 ++++++++ app/src/main/res/values-fr/strings.xml | 24 ++++++++- app/src/main/res/values-in/strings.xml | 22 ++++++++ app/src/main/res/values-it/strings.xml | 22 ++++++++ app/src/main/res/values-ja/strings.xml | 22 ++++++++ app/src/main/res/values-ko/strings.xml | 22 ++++++++ app/src/main/res/values-nl/strings.xml | 24 ++++++++- app/src/main/res/values-pl/strings.xml | 22 ++++++++ app/src/main/res/values-pt/strings.xml | 26 +++++++++- app/src/main/res/values-ro/strings.xml | 22 ++++++++ app/src/main/res/values-ru/strings.xml | 40 +++++++++++---- app/src/main/res/values-th/strings.xml | 22 ++++++++ app/src/main/res/values-vi/strings.xml | 59 +++++++++++++++------- app/src/main/res/values-zh-rCN/strings.xml | 22 ++++++++ app/src/main/res/values-zh-rTW/strings.xml | 22 ++++++++ 17 files changed, 423 insertions(+), 42 deletions(-) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 72003cf5d9a..c153122007e 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -5618,13 +5618,13 @@ يبدأ الاجتماع الآن - Share the decryption key too? + مشاركة مفتاح فك التشفير أيضا؟ - The decryption key has been exported and will be shared with the link. + تم تصدير مفتاح فك التشفير وستتم مشاركته مع الرابط - Share link only + شارك الرابط فقط - Share link and key + شارك الرابط مع المفتاح شارك الرابط @@ -5636,8 +5636,12 @@ - Link copied to clipboard - Links copied to clipboard + تم نسخ الروابط إلى الحافظة + تم نسخ الرابط إلى الحافظة + تم نسخ الروابط إلى الحافظة + تم نسخ الروابط إلى الحافظة + تم نسخ الروابط إلى الحافظة + تم نسخ الروابط إلى الحافظة @@ -5650,8 +5654,12 @@ - People you shared this link with, will lose access to the album. - People you shared these links with, will lose access to the albums. + سيفقد الأشخاص الذين شاركت معهم هذه الروابط إمكانية الوصول إلى الألبومات. + سيفقد الأشخاص الذين شاركت معهم هذا الرابط إمكانية الوصول إلى الألبومات. + سيفقد الأشخاص الذين شاركت معهم هذه الروابط إمكانية الوصول إلى الألبومات. + سيفقد الأشخاص الذين شاركت معهم هذه الروابط إمكانية الوصول إلى الألبومات. + سيفقد الأشخاص الذين شاركت معهم هذه الروابط إمكانية الوصول إلى الألبومات. + سيفقد الأشخاص الذين شاركت معهم هذه الروابط إمكانية الوصول إلى الألبومات. @@ -5665,5 +5673,27 @@ أدخل مفتاح فك التشفير - To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + للوصول إلى الألبوم ، أدخل مفتاح فك التشفير. إذا لم يكن لديك، فاتصل بالشخص الذي شارك الرابط معك. + + Remember preferences + + + + + + كل يوم + + كل أسبوع + + كل شهر + + تخصيص + + يومياً + + أسبوعياً + + شهرياً + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 789b69dbe2c..3a950389f5a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -4813,5 +4813,27 @@ Schlüssel eingeben - To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + Um auf das Album zuzugreifen, geben Sie den Schlüssel für die Entschlüsselung ein. Wenn Sie keinen Schlüssel haben, wenden Sie sich an die Person, die Ihnen den Link mitgeteilt hat. + + Remember preferences + + + + + + Täglich + + Wöchentlich + + Monatlich + + Benutzerdefiniert + + Täglich + + Wöchentlich + + monatlich + + \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index fcc73362142..be4955b514e 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -5029,4 +5029,26 @@ Introducir la clave de descifrado To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + + Remember preferences + + + + + + Cada día + + Todas las semanas + + Todos los meses + + Personalizar + + Diaria + + Semanal + + Mensual + + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 91724648ce9..6d9c66e08e5 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -5028,5 +5028,27 @@ Saisir la clé de déchiffrement - To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + Pour accéder à l’album, saisissez la clé de déchiffrement. Si vous ne l’avez pas, contactez la personne qui a partagé le lien avec vous. + + Mémoriser les préférences + + + + + + Quotidienne + + Hebdomadaire + + Mensuelle + + Personnalisée + + Quotidienne + + Hebdomadaire + + Mensuelle + + \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 3f3d8000468..f363a7800ff 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -4601,4 +4601,26 @@ Masukkan kunci dekripsi To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + + Remember preferences + + + + + + Every day + + Every week + + Every month + + Kustom + + Harian + + Mingguan + + Bulanan + + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index b0b814a0703..eca7e34674c 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -5027,4 +5027,26 @@ Inserisci la chiave di decrittazione To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + + Remember preferences + + + + + + Ogni giorno + + Ogni settimana + + Ogni mese + + Personalizzato + + Giornalmente + + Settimanalmente + + Mensilmente + + \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index db1f8c96d9f..416160e978b 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -4599,4 +4599,26 @@ 復号キーを入力してください アルバムにアクセスするには、復号化キーを入力します。お持ちでない場合は、リンクを共有した人に連絡してください。 + + 設定を記憶する + + + + + + 毎日 + + 毎週 + + 毎月 + + カスタム + + 毎日 + + 毎週 + + 月間 + + \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index be85d192c8b..3ab890771a5 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -4599,4 +4599,26 @@ 복호화 키를 입력하세요 To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + + Remember preferences + + + + + + 매일 + + 매주 + + 매달 + + 이용자 지정 + + 매일 + + 매주 + + 매월 + + \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 3427f0f6cc3..dde80f3d1c4 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -4813,5 +4813,27 @@ Vul decoderingssleutel in - To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + Voer de decoderingssleutel in om toegang te krijgen tot het album. Als u deze niet hebt, neem dan contact op met de persoon die de link met u heeft gedeeld. + + Voorkeuren onthouden + + + + + + Elke dag + + Elke week + + Elke maand + + Aangepast + + Dagelijks + + Wekelijks + + Maandelijks + + \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 6da20833ed5..45239aa9496 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -5242,4 +5242,26 @@ Wprowadź klucz deszyfrujący To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + + Remember preferences + + + + + + Każdego dnia + + Każdego tygodnia + + Każdego miesiąca + + Dowolne + + Codziennie + + Tygodniowo + + Miesięcznie + + \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 78efcd0fb9b..e7c07f5ac07 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1361,7 +1361,7 @@ Usar dados celulares - Upload de vídeos + Fazer upload de vídeos Foto de perfil atualizada @@ -5028,5 +5028,27 @@ Digite a chave de decodificação - To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + Para acessar o álbum, digite a chave de decodificação. Se você não tiver a chave, entre em contato com a pessoa que compartilhou o link com você. + + Salvar as preferências + + + + + + Todo dia + + Toda semana + + Todo mês + + Personalizado + + Diária + + Semanal + + Mensal + + \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 5f9cb53d943..c033e914cc2 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -4992,4 +4992,26 @@ Introdu cheia de decriptare To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + + Remember preferences + + + + + + Every day + + Every week + + Every month + + Personalizat + + Zilnică + + Săptămânală + + Lunară + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 8b3e7f081c6..e743aee59e2 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -993,20 +993,20 @@ Пока я не включу их снова - Уведомления чата будут отключены на %s + Уведомления чата станут беззвучными на %s - Уведомления чата будут отключены до %1$s - Уведомления чата будут отключены до %1$s - Уведомления чата будут отключены до %1$s - Уведомления чата будут отключены до %1$s + Уведомления чата будут беззвучными до %1$s + Уведомления чата будут беззвучными до %1$s + Уведомления чата будут беззвучными до %1$s + Уведомления чата будут беззвучными до %1$s - Уведомления чата будут отключены до %1$s %2$s - Уведомления чата будут отключены до %1$s %2$s - Уведомления чата будут отключены до %1$s %2$s - Уведомления чата будут отключены до %1$s %2$s + Уведомления чата будут беззвучными до %1$s %2$s + Уведомления чата будут беззвучными до %1$s %2$s + Уведомления чата будут беззвучными до %1$s %2$s + Уведомления чата будут беззвучными до %1$s %2$s Уведомления чата отключены @@ -5242,4 +5242,26 @@ Введите ключ дешифрирования To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + + Remember preferences + + + + + + Каждый день + + Каждую неделю + + Каждый месяц + + Другой срок + + Ежедневно + + Еженедельно + + Ежемесячно + + \ No newline at end of file diff --git a/app/src/main/res/values-th/strings.xml b/app/src/main/res/values-th/strings.xml index 910b6592b32..607667f2210 100644 --- a/app/src/main/res/values-th/strings.xml +++ b/app/src/main/res/values-th/strings.xml @@ -4601,4 +4601,26 @@ กรอกคีย์เพื่อถอดรหัสลับ To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + + Remember preferences + + + + + + Every day + + Every week + + Every month + + กำหนดเอง + + รายวัน + + รายสัปดาห์ + + รายเดือน + + \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 911bce2d9d4..ca80208b423 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -1114,7 +1114,7 @@ Đăng tải tệp đính kèm - Không có lịch sử trò chuyện + Không có lịch sử trò chuyện %1$s [A]đang viết…[/A] @@ -1251,7 +1251,7 @@ Lưu vào Ổ Mây - Xem sổ liên lạc + Xem sổ liên lạc Lỗi. Chưa thêm vào Ổ Mây được @@ -1471,7 +1471,7 @@ Mẫu mã thiết bị - Phiên bản Android + Phiên bản Android Tệp văn bản mới @@ -1679,7 +1679,7 @@ Phiên bản lưu - Phiên bản hiện tại + Phiên bản hiện tại Lich sử phiên bản của các tệp tin @@ -1956,7 +1956,7 @@ Lỗi phát sinh. Mục không thể khôi phục. - Gửi tin nhắn + Gửi tin nhắn Hành động này không thể hoàn tất vì sẽ chiếm dụng toàn bộ không gian lưu trữ của tài khoản. Có muốn tiến hành nâng cấp hạng tài khoản để tiếp tục hay không? @@ -2518,7 +2518,7 @@ Việc phát nội dung phương tiện số không thực hiện được khi có cuộc gọi đang diễn ra. - Cuộc gọI đang diễn ra + Cuộc gọi đang diễn ra Chạm vào để tham gia cuộc gọi nhóm. @@ -2658,7 +2658,7 @@ Bộ quản lý nhân viên cỉ hỗ trợ trên phiên bản trình duyệt web dành cho máy tính. - Liệt kê lượng dùng + Liệt kê lượng dùng Truyền Tải @@ -2690,7 +2690,7 @@ Xin chọn một hay nhiều tên liên lạc. - Gửi đến %s tên liên lạc. + Đã gửi %s tên liên lạc. Tệp đã gửi trong chát @@ -2724,7 +2724,7 @@ Biêt danh - Số điện thoại + Số điện thoại Đang kết nối lại @@ -2766,7 +2766,7 @@ Tệp tin không có tìm thấy trong Ổ Mây của bạn. - Hết không gian lưu trữ + Không gian lưu trữ đầy Dữ liệu của bạn có nguy cơ bị xóa! @@ -2858,13 +2858,13 @@ Không có ảnh GIF nào. Xin thử lại sau - [A]Hết[/A] kết quả + [A]Hết[/A] kết quả Tiếp tục các truyền tải? Tiếp tục các truyền tải - Hủy truyền tải + Hủy truyền tải Các truyền tải bị ngừng sẽ được tiếp tục tải lên. @@ -4568,13 +4568,13 @@ Cuộc họp bắt đầu ngay bây giờ - Share the decryption key too? + Có muốn chia sẻ chìa khóa giải mã luôn không? - The decryption key has been exported and will be shared with the link. + Chìa khóa giải mã đã được xuất ra và sẽ được chia sẻ với đường liên kết. - Share link only + Chỉ chia sẻ đường liên kết - Share link and key + Chia sẻ đường liên kết và chìa khóa Chia sẻ đường liên kết @@ -4589,8 +4589,7 @@ - People you shared this link with, will lose access to the album. - People you shared these links with, will lose access to the albums. + Tất cả những người bạn đã chia sẻ các đường liên kết này sẽ không thể truy cập vào album được nữa @@ -4599,5 +4598,27 @@ Nhập chìa khóa giải mã - To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + Để truy cập vào album, nhập chìa khóa giải mã. Nếu bạn không có, hãy liên hệ với người đã chia sẻ đường liên kết cho bạn. + + Ghi nhớ các tùy chỉnh + + + + + + Mỗi ngày + + Mỗi tuần + + Mỗi tháng + + Tùy chỉnh + + Hằng ngày + + Hằng tuần + + Hàng tháng + + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a2ee0119a5b..5c174175489 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -4599,4 +4599,26 @@ 请输入解密密钥 要访问该相册,请输入解密密钥。如果您没有,请联系与您共享链接的人。 + + 记住偏好设置 + + + + + + 每日 + + 每周 + + 每月 + + 自定义 + + 每日 + + 每周 + + 每月 + + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 15130373b5a..3a95faae994 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -4599,4 +4599,26 @@ 請輸入解密金鑰 要存取相簿,請輸入解密金鑰。如果您沒有金鑰,請聯繫共享金鑰給您的人。 + + 記住偏好設定 + + + + + + 每日 + + 每週 + + 每月 + + 自訂 + + 每日 + + 每週 + + 每月 + + \ No newline at end of file From 0f7473f676ff04b65ad0ab666351eeb54361ca5c Mon Sep 17 00:00:00 2001 From: Veronika Koreiba Date: Wed, 31 May 2023 07:51:03 +1200 Subject: [PATCH 307/334] Pre-release v8.2 - Update available translations --- app/src/main/res/values-in/strings.xml | 30 ++++++++++++-------------- app/src/main/res/values-ru/strings.xml | 18 +++++++++------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index f363a7800ff..20a6fe11ebf 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -4504,9 +4504,9 @@ Nama rapat wajib diisi - Enter up to 30 characters + Masukan hingga 30 karakter - Enter up to 3,000 characters + Masukan hingga 3.000 karakter Rapat dijadwalkan @@ -4554,7 +4554,7 @@ tidak dapat hapus selamanya %1$d barang - Enter time + Masukan waktu Undangan rapat @@ -4568,21 +4568,20 @@ Rapat dimulai sekarang - Share the decryption key too? + Bagikan kunci dekripsi juga? - The decryption key has been exported and will be shared with the link. + Kunci dekripsi telah diekspor dan akan dibagikan dengan tautan. - Share link only + Bagikan tautan saja - Share link and key + Bagikan tautan dan kunci Bagikan link - Link copied to clipboard - Links copied to clipboard + Tautan disalin ke papan klip @@ -4590,8 +4589,7 @@ - People you shared this link with, will lose access to the album. - People you shared these links with, will lose access to the albums. + Orang yang anda bagikan tautan ini akan kehilangan akses ke album. @@ -4600,19 +4598,19 @@ Masukkan kunci dekripsi - To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + Untuk mengakses album, masukkan kunci dekripsi. Jika anda tidak memilikinya, hubungi orang yang membagikan tautan tersebut dengan anda. - Remember preferences + Ingat preferensi - Every day + Setiap hari - Every week + Setiap minggu - Every month + Setiap bulan Kustom diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e743aee59e2..6c1057f1f50 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -5198,13 +5198,13 @@ Встреча начинается - Share the decryption key too? + Поделиться ключом дешифрования тоже? - The decryption key has been exported and will be shared with the link. + Ключ дешифрования экспортирован и будет передан вместе со ссылкой. - Share link only + Поделиться только ссылкой - Share link and key + Поделиться ссылкой и ключом Поделиться ссылкой @@ -5228,8 +5228,10 @@ - People you shared this link with, will lose access to the album. - People you shared these links with, will lose access to the albums. + Люди, с которыми вы поделились этой ссылкой, потеряют доступ к альбому. + Люди, с которыми вы поделились этими ссылками, потеряют доступ к альбомам. + Люди, с которыми вы поделились этими ссылками, потеряют доступ к альбомам. + Люди, с которыми вы поделились этими ссылками, потеряют доступ к альбомам. @@ -5241,9 +5243,9 @@ Введите ключ дешифрирования - To access the album, enter the decryption key. If you don’t have it, contact the person who shared the link with you. + Для доступа к альбому введите ключ дешифрования. Если у вас его нет, обратитесь к человеку, который поделился с вами ссылкой. - Remember preferences + Запомнить настройки From f74e1412262d5e750c0a6e3c7c7003f79865da28 Mon Sep 17 00:00:00 2001 From: Veronika Koreiba Date: Wed, 31 May 2023 09:25:18 +1200 Subject: [PATCH 308/334] Update SDK and MEGAchat submodules --- build.gradle | 2 +- sdk/src/main/jni/mega/sdk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index cb3bc3d89de..0f2fc9bea13 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230525.211514-rel' + megaSdkVersion = '20230530.210831-rel' //JDK and Java Version jdk = "17" diff --git a/sdk/src/main/jni/mega/sdk b/sdk/src/main/jni/mega/sdk index a46a9d45fac..2b11db67190 160000 --- a/sdk/src/main/jni/mega/sdk +++ b/sdk/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit a46a9d45fac09f8e95a63fa8e0d53b1b1ab22b9f +Subproject commit 2b11db671904d3d25f05e5c76b90ceab7f9d86fd From 01ddcb1adbd500aea4faaeadcce9fa75f6c96ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yenel=20Rodr=C3=ADguez=20Hern=C3=A1ndez?= Date: Fri, 2 Jun 2023 00:36:51 +1200 Subject: [PATCH 309/334] Hotfix: AND-16599 Create data layer methods and mappers --- .../data/mapper/analytics/AnalyticsEventMessageMapper.kt | 1 + .../data/mapper/analytics/AnalyticsEventMessageMapperTest.kt | 1 + .../privacy/android/domain/repository/StatisticsRepository.kt | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/data/src/main/java/mega/privacy/android/data/mapper/analytics/AnalyticsEventMessageMapper.kt b/data/src/main/java/mega/privacy/android/data/mapper/analytics/AnalyticsEventMessageMapper.kt index 36b74992045..c4ab5743d17 100644 --- a/data/src/main/java/mega/privacy/android/data/mapper/analytics/AnalyticsEventMessageMapper.kt +++ b/data/src/main/java/mega/privacy/android/data/mapper/analytics/AnalyticsEventMessageMapper.kt @@ -22,4 +22,5 @@ class AnalyticsEventMessageMapper @Inject constructor( * @return */ operator fun invoke(event: AnalyticsEvent): String = gson.toJson(event.data()) + .replace("\"", "\\\"") } diff --git a/data/src/test/java/mega/privacy/android/data/mapper/analytics/AnalyticsEventMessageMapperTest.kt b/data/src/test/java/mega/privacy/android/data/mapper/analytics/AnalyticsEventMessageMapperTest.kt index e450658f6dd..3dee95b1354 100644 --- a/data/src/test/java/mega/privacy/android/data/mapper/analytics/AnalyticsEventMessageMapperTest.kt +++ b/data/src/test/java/mega/privacy/android/data/mapper/analytics/AnalyticsEventMessageMapperTest.kt @@ -31,6 +31,7 @@ internal class AnalyticsEventMessageMapperTest { } val expected = """{"string":"string","int":1,"long":1,"null":null,"array":[1,2,3]}""" + .replace("\"", "\\\"") assertThat(underTest(event)).isEqualTo(expected) } diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/repository/StatisticsRepository.kt b/domain/src/main/kotlin/mega/privacy/android/domain/repository/StatisticsRepository.kt index 85edfc86377..1463e1bd891 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/repository/StatisticsRepository.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/repository/StatisticsRepository.kt @@ -20,7 +20,7 @@ interface StatisticsRepository { * Send an event to the stats server. * * @param eventId Event type - * @param message Event message + * @param message Event message. If the message contains quotes, they must be escaped quotes. * @param addJourneyId True if JourneyID should be included. Otherwise, false. * @param viewId ViewID value (C-string null-terminated) to be sent with the event. * This value should have been generated with [this.generateViewId]. From 5d8d1f040545410aab60443f6b86e892dfa31657 Mon Sep 17 00:00:00 2001 From: Hai Luong Date: Fri, 2 Jun 2023 08:14:29 +0700 Subject: [PATCH 310/334] AND-16695: Cancel last completed transfer should show "NO COMPLETED TRANSFERS" (cherry picked from commit 5f8d6fb01ea551bdf04d03c2179eaf9afdb71a3d) --- .../app/main/managerSections/CompletedTransfersFragment.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/mega/privacy/android/app/main/managerSections/CompletedTransfersFragment.kt b/app/src/main/java/mega/privacy/android/app/main/managerSections/CompletedTransfersFragment.kt index 5b8ac09be7c..089faa4d5ea 100644 --- a/app/src/main/java/mega/privacy/android/app/main/managerSections/CompletedTransfersFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/main/managerSections/CompletedTransfersFragment.kt @@ -79,6 +79,7 @@ class CompletedTransfersFragment : TransfersBaseFragment() { } is CompletedTransfersState.TransferRemovedUpdated -> { adapter.removeItemData(transfersState.index, transfersState.newTransfers) + setEmptyView(transfersState.newTransfers.size) } is CompletedTransfersState.ClearTransfersUpdated -> { adapter.setCompletedTransfers(emptyList()) From 3cb9d009e47ed86a4f752c8949581fbc73fc3f2e Mon Sep 17 00:00:00 2001 From: Nikhil Nagori Date: Fri, 2 Jun 2023 18:44:41 +1200 Subject: [PATCH 311/334] FM-436 Fix multi file upload not working --- app/src/main/java/mega/privacy/android/app/ShareInfo.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/ShareInfo.java b/app/src/main/java/mega/privacy/android/app/ShareInfo.java index 8bb902e5b8b..8216e5ae04f 100644 --- a/app/src/main/java/mega/privacy/android/app/ShareInfo.java +++ b/app/src/main/java/mega/privacy/android/app/ShareInfo.java @@ -135,8 +135,8 @@ public static List processIntent(Intent intent, Context context) { return processIntentMultiple(intent, context); } } else if (intent.getClipData() != null) { - if (Intent.ACTION_GET_CONTENT.equals(intent.getAction())) { - Timber.d("Multiple ACTION_GET_CONTENT"); + if (Intent.ACTION_GET_CONTENT.equals(intent.getAction()) || Intent.ACTION_OPEN_DOCUMENT.equals(intent.getAction())) { + Timber.d("Multiple ACTION"); return processGetContentMultiple(intent, context); } } From bbca7af1177947a0c89bc25220feaff983bb5aad Mon Sep 17 00:00:00 2001 From: Yenel Date: Fri, 2 Jun 2023 11:22:26 +0200 Subject: [PATCH 312/334] AP-213 AND - set up MEGA page should not show again after clicking "not now" --- .../android/app/main/ManagerActivity.kt | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt index 7295a5f0930..02427d60f3b 100644 --- a/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/main/ManagerActivity.kt @@ -663,6 +663,7 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf private var joiningToChatLink = false private var linkJoinToChatLink: String? = null private var onAskingPermissionsFragment = false + private var initialPermissionsAlreadyAsked = false private lateinit var navHostView: View private var navController: NavController? = null private var mHomepageSearchable: HomepageSearchable? = null @@ -1436,15 +1437,10 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf return true } prefs = dbH.preferences - firstTimeAfterInstallation = if (prefs == null) { - true - } else { - if (prefs?.firstTime == null) { - true - } else { - prefs?.firstTime.toBoolean() - } - } + firstTimeAfterInstallation = + if (prefs == null || prefs?.firstTime == null) true + else prefs?.firstTime.toBoolean() + return false } @@ -2446,17 +2442,21 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf * have not been shown. */ private fun checkInitialScreens() { - if (checkBusinessStatus()) { - myAccountInfo.isBusinessAlertShown = true - return - } - if (firstTimeAfterInstallation || askPermissions) { - if (canVerifyPhoneNumber().not() || onAskingPermissionsFragment || newCreationAccount) { - drawerItem = DrawerItem.ASK_PERMISSIONS - askForAccess() + when { + checkBusinessStatus() -> { + myAccountInfo.isBusinessAlertShown = true + } + + firstTimeAfterInstallation || askPermissions || newCreationAccount -> { + if (!initialPermissionsAlreadyAsked && !onAskingPermissionsFragment) { + drawerItem = DrawerItem.ASK_PERMISSIONS + askForAccess() + } + } + + requestNotificationsPermissionFirstLogin -> { + askForNotificationsPermission() } - } else if (requestNotificationsPermissionFirstLogin) { - askForNotificationsPermission() } } @@ -2696,6 +2696,7 @@ class ManagerActivity : TransfersManagementActivity(), MegaRequestListenerInterf } fun destroyPermissionsFragment() { + initialPermissionsAlreadyAsked = true //In mobile, allow all orientation after permission screen if (!Util.isTablet(this)) { Timber.d("Mobile, all orientation") From e1036a834c05bb2b346843597df29e9ebd609cc0 Mon Sep 17 00:00:00 2001 From: Raquel Garcia Chico Date: Fri, 2 Jun 2023 23:22:16 +1200 Subject: [PATCH 313/334] Hotfix: MEET-2506 key:meetings_schedule_meeting_recurrence_monthly_description needs to be pluralised --- .../meeting/view/ScheduleMeetingView.kt | 5 +- app/src/main/res/values/strings.xml | 50 +++---------------- 2 files changed, 11 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/meeting/view/ScheduleMeetingView.kt b/app/src/main/java/mega/privacy/android/app/presentation/meeting/view/ScheduleMeetingView.kt index 171ba2318dc..bf4bba51a1f 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/meeting/view/ScheduleMeetingView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/meeting/view/ScheduleMeetingView.kt @@ -364,8 +364,9 @@ private fun ActionButton( style = MaterialTheme.typography.subtitle2, fontSize = 11.sp, fontWeight = FontWeight.Normal, - text = stringResource( - id = R.string.meetings_schedule_meeting_recurrence_monthly_description, + text = pluralStringResource( + R.plurals.meetings_schedule_meeting_recurrence_monthly_description, + state.startDate.dayOfMonth, state.startDate.dayOfMonth ), color = MaterialTheme.colors.textColorSecondary diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f3d628c9299..e81f37b8b88 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -414,8 +414,6 @@ Moved - %d items weren’t moved - Moved to Rubbish bin Error. Not moved @@ -427,14 +425,6 @@ Deleted Error. Deletion failed - - %d items removed from MEGA - - %d items weren’t removed - - %d folders left - - %d folders weren’t left %d items copied @@ -938,8 +928,6 @@ %d invite sent %d invites sent - - %1$d invite requests sent but %2$d requests weren’t sent. %1s (Me) @@ -2264,10 +2252,6 @@ [A]Your publicly shared file [/A][B]%s[/B] has been taken down. [A]Your publicly shared folder [/A][B]%s[/B] has been taken down. - - This file has been the subject of a takedown notice. - - This folder has been the subject of a takedown notice. Dispute takedown @@ -2717,10 +2701,6 @@ Please wait… Business - - Admin - - User Status @@ -3386,10 +3366,6 @@ View meeting chat Invite more participants to the meeting. Swipe up to invite. - - Tap to create a new meeting - - Quickly set up a MEGA meeting with our new encrypted meeting feature You are the new host @@ -4518,17 +4494,10 @@ Today, %1$s from %2$s to %3$s Tomorrow, %1$s from %2$s to %3$s - - [A]%1$s updated[/A][B] the recurring meeting date[/B] Account security upgrade We’re upgrading your account’s security. You should see this message only once. If you’ve seen it before, first make sure it has been for this account and not for another MEGA account you have.\n\nIf you’re sure and it’s the second time you’re seeing this message for this account, stop using this account. - - - You’re currently sharing the following folder: %s - You’re currently sharing the following folders: %s - [Undecrypted file] @@ -4604,18 +4573,12 @@ Couldn’t copy %1$d item Couldn’t copy %1$d items - + Copied %1$d item,  Copied %1$d items,  - + couldn’t copy %1$d item couldn’t copy %1$d items @@ -4802,8 +4765,8 @@ - People you shared this link with, will lose access to the album. - People you shared these links with, will lose access to the albums. + People you shared this link with will lose access to the album. + People you shared these links with will lose access to the albums. @@ -4835,7 +4798,10 @@ Monthly - Some months don’t have %1$d days. For these months, the occurrence will be scheduled for the last day of the month. + + Some months don’t have %1$d day. For these months, the occurrence will be scheduled for the last day of the month. + Some months don’t have %1$d days. For these months, the occurrence will be scheduled for the last day of the month. + Cannot open folder From 47b59efb2fb7edbedc3d0055162e224495dcd3c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raquel=20Garc=C3=ADa=20Chico?= Date: Fri, 2 Jun 2023 14:40:26 +0200 Subject: [PATCH 314/334] T10397800 Accept a group call from notifications --- .../app/meeting/activity/MeetingActivity.kt | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/meeting/activity/MeetingActivity.kt b/app/src/main/java/mega/privacy/android/app/meeting/activity/MeetingActivity.kt index ce574c8774b..9e1649d9032 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/activity/MeetingActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/activity/MeetingActivity.kt @@ -120,20 +120,24 @@ class MeetingActivity : BaseActivity() { currentFragment.releaseVideoAndHideKeyboard() removeRTCAudioManager() } + is JoinMeetingFragment -> { currentFragment.releaseVideoDeviceAndRemoveChatVideoListener() removeRTCAudioManager() } + is JoinMeetingAsGuestFragment -> { currentFragment.releaseVideoAndHideKeyboard() removeRTCAudioManager() } + is InMeetingFragment -> { // Prevent guest from quitting the call by pressing back if (!isGuest) { currentFragment.removeUI() } } + is MakeModeratorFragment -> { currentFragment.cancel() } @@ -203,7 +207,27 @@ class MeetingActivity : BaseActivity() { } private fun initIntent() { - intent?.let { it -> + intent?.let { + if (it.action == CallNotificationIntentService.ANSWER) { + it.extras?.let { extra -> + val chatIdIncomingCall = extra.getLong( + Constants.CHAT_ID_OF_INCOMING_CALL, + MEGACHAT_INVALID_HANDLE + ) + + val answerIntent = Intent(this, CallNotificationIntentService::class.java) + answerIntent.putExtra( + Constants.CHAT_ID_OF_CURRENT_CALL, + MEGACHAT_INVALID_HANDLE + ) + answerIntent.putExtra(Constants.CHAT_ID_OF_INCOMING_CALL, chatIdIncomingCall) + answerIntent.action = it.action + startService(answerIntent) + finish() + return + } + } + if (it.action == CallNotificationIntentService.START_SCHED_MEET) { it.extras?.let { extra -> val chatIdIncomingCall = extra.getLong( @@ -287,6 +311,7 @@ class MeetingActivity : BaseActivity() { MEETING_ACTION_CREATE, MEETING_ACTION_JOIN, MEETING_ACTION_GUEST -> { actionBar.setHomeAsUpIndicator(R.drawable.ic_close_white) } + MEETING_ACTION_IN -> actionBar.setHomeAsUpIndicator(R.drawable.ic_arrow_back_white) } } From 0e37b37df2aefb1fc4007223ec0397897a925792 Mon Sep 17 00:00:00 2001 From: Nikhil Nagori Date: Tue, 6 Jun 2023 04:14:13 +1200 Subject: [PATCH 315/334] AND-16063 Fix app not navigating to folder selected as the target for last copy/move action --- .../domain/usecase/account/GetCopyLatestTargetPathUseCase.kt | 2 +- .../domain/usecase/account/GetMoveLatestTargetPathUseCase.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/account/GetCopyLatestTargetPathUseCase.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/account/GetCopyLatestTargetPathUseCase.kt index f2705ba403f..170e969bb5a 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/account/GetCopyLatestTargetPathUseCase.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/account/GetCopyLatestTargetPathUseCase.kt @@ -16,6 +16,6 @@ class GetCopyLatestTargetPathUseCase @Inject constructor( */ suspend operator fun invoke(): Long? { val path = accountRepository.getLatestTargetPathCopyPreference() - return path?.takeIf { isNodeInRubbishOrDeletedUseCase(it) } + return path?.takeIf { !isNodeInRubbishOrDeletedUseCase(it) } } } \ No newline at end of file diff --git a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/account/GetMoveLatestTargetPathUseCase.kt b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/account/GetMoveLatestTargetPathUseCase.kt index 77f54a94cd5..0d45cb6c4c7 100644 --- a/domain/src/main/kotlin/mega/privacy/android/domain/usecase/account/GetMoveLatestTargetPathUseCase.kt +++ b/domain/src/main/kotlin/mega/privacy/android/domain/usecase/account/GetMoveLatestTargetPathUseCase.kt @@ -16,6 +16,6 @@ class GetMoveLatestTargetPathUseCase @Inject constructor( */ suspend operator fun invoke(): Long? { val path = accountRepository.getLatestTargetPathMovePreference() - return path?.takeIf { isNodeInRubbishOrDeletedUseCase(it) } + return path?.takeIf { !isNodeInRubbishOrDeletedUseCase(it) } } } \ No newline at end of file From 49b0873461b3dad7cbe23f1b2b319f44424fb4d2 Mon Sep 17 00:00:00 2001 From: Kevin Sun Date: Tue, 6 Jun 2023 11:11:00 +0800 Subject: [PATCH 316/334] T10394375 Bug: Audio Player Pause/Resume Behaviour on Receiving Calls while app is in Background --- .../android/app/mediaplayer/service/MediaPlayerService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerService.kt b/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerService.kt index 11f97893206..b5753aac04e 100644 --- a/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerService.kt +++ b/app/src/main/java/mega/privacy/android/app/mediaplayer/service/MediaPlayerService.kt @@ -108,7 +108,7 @@ abstract class MediaPlayerService : LifecycleService(), LifecycleEventObserver, // We need keep it as Runnable here, because we need remove it from handler later, // using lambda doesn't work when remove it from handler. private val resumePlayRunnable = Runnable { - if (needPlayWhenReceiveResumeCommand) { + if (needPlayWhenReceiveResumeCommand || !mediaPlayerGateway.getPlayWhenReady()) { setPlayWhenReady(true) needPlayWhenReceiveResumeCommand = false } From 129233875e53ffb6fb031e1686554c607fe00919 Mon Sep 17 00:00:00 2001 From: rohitsoni Date: Mon, 5 Jun 2023 16:28:38 +0530 Subject: [PATCH 317/334] FM-1 Unify options for Disputed files with other platforms --- .../app/presentation/view/NodeGridViewItem.kt | 42 ++++++-- app/src/main/res/layout/item_file_list.xml | 96 +++++++++---------- 2 files changed, 81 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/view/NodeGridViewItem.kt b/app/src/main/java/mega/privacy/android/app/presentation/view/NodeGridViewItem.kt index 752fd8582ef..6b546f8fe72 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/view/NodeGridViewItem.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/view/NodeGridViewItem.kt @@ -20,12 +20,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension +import androidx.constraintlayout.compose.Visibility import mega.privacy.android.app.R import mega.privacy.android.app.presentation.data.NodeUIItem import mega.privacy.android.app.presentation.photos.albums.view.MiddleEllipsisText @@ -72,7 +74,7 @@ internal fun NodeGridViewItem( ) .padding(start = 16.dp, end = 8.dp), ) { - val (menuImage, txtTitle, thumbImage) = createRefs() + val (menuImage, txtTitle, thumbImage, takenDownImage) = createRefs() Image( painter = painterResource(id = R.drawable.ic_dots_vertical_grey), contentDescription = "3 dots", @@ -93,17 +95,29 @@ internal fun NodeGridViewItem( .height(24.dp) .width(24.dp) .constrainAs(thumbImage) { - start.linkTo(parent.start) + end.linkTo(menuImage.start) top.linkTo(parent.top) bottom.linkTo(parent.bottom) } ) + Image( + modifier = Modifier.constrainAs(takenDownImage) { + end.linkTo(menuImage.start) + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + visibility = if (nodeUIItem.isTakenDown) Visibility.Visible else Visibility.Gone + } + .height(16.dp) + .width(16.dp), + painter = painterResource(id = R.drawable.ic_taken_down), + colorFilter = ColorFilter.tint(MaterialTheme.colors.red_800_red_400), + contentDescription = "Taken Down") MiddleEllipsisText( text = nodeUIItem.name, modifier = Modifier - .padding(horizontal = 8.dp) + .padding(end = 8.dp) .constrainAs(txtTitle) { - end.linkTo(menuImage.start) + end.linkTo(takenDownImage.start) start.linkTo(thumbImage.end) top.linkTo(parent.top) bottom.linkTo(parent.bottom) @@ -153,7 +167,7 @@ internal fun NodeGridViewItem( .padding(start = 16.dp, end = 8.dp, bottom = 16.dp, top = 16.dp) .fillMaxWidth() ) { - val (menuImage, txtTitle) = createRefs() + val (menuImage, txtTitle, takenDownImage) = createRefs() Image( painter = painterResource(id = R.drawable.ic_dots_vertical_grey), contentDescription = "3 dots", @@ -163,13 +177,27 @@ internal fun NodeGridViewItem( end.linkTo(parent.end) } ) + Image( + modifier = Modifier.constrainAs(takenDownImage) { + end.linkTo(menuImage.start) + top.linkTo(parent.top) + bottom.linkTo(parent.bottom) + visibility = + if (nodeUIItem.isTakenDown) Visibility.Visible else Visibility.Gone + } + .height(16.dp) + .width(16.dp), + painter = painterResource(id = R.drawable.ic_taken_down), + colorFilter = ColorFilter.tint(MaterialTheme.colors.red_800_red_400), + contentDescription = "Taken Down") MiddleEllipsisText( text = nodeUIItem.name, modifier = Modifier - .padding(horizontal = 8.dp) + .padding(end = 8.dp) .constrainAs(txtTitle) { - end.linkTo(menuImage.start) start.linkTo(parent.start) + end.linkTo(takenDownImage.start) + width = Dimension.fillToConstraints }, style = MaterialTheme.typography.subtitle1, maxLines = 1, diff --git a/app/src/main/res/layout/item_file_list.xml b/app/src/main/res/layout/item_file_list.xml index ecca9123cd3..5d3021a645a 100644 --- a/app/src/main/res/layout/item_file_list.xml +++ b/app/src/main/res/layout/item_file_list.xml @@ -70,7 +70,7 @@ app:tint="?android:attr/textColorSecondary" tools:ignore="ContentDescription" /> - - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + android:src="@drawable/ic_taken_down" + tools:ignore="ContentDescription" + app:tint="@color/red_800_red_400" + android:visibility="gone" + tools:visibility="visible"/> + android:layout_marginEnd="3dp" + app:layout_constraintEnd_toStartOf="@id/file_list_taken_down" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + android:src="@drawable/link_ic" + app:tint="?android:attr/textColorSecondary" + tools:ignore="ContentDescription" /> - - - - + android:id="@+id/img_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="3dp" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/img_favourite" + android:src="@drawable/ic_circle_label" + android:visibility="gone" + tools:visibility="visible" /> + + Date: Tue, 6 Jun 2023 10:46:20 +0530 Subject: [PATCH 318/334] FM-279 User should not able to open disputed Folder and file --- .../app/fragments/homepage/ItemOperationViewModel.kt | 6 +++--- .../android/app/fragments/homepage/audio/AudioFragment.kt | 5 ++--- .../app/fragments/homepage/documents/DocumentsFragment.kt | 8 ++++---- .../android/app/fragments/homepage/video/VideoFragment.kt | 7 ++++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/homepage/ItemOperationViewModel.kt b/app/src/main/java/mega/privacy/android/app/fragments/homepage/ItemOperationViewModel.kt index 30cbf03461f..6eaa2906d0c 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/homepage/ItemOperationViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/homepage/ItemOperationViewModel.kt @@ -15,15 +15,15 @@ class ItemOperationViewModel @Inject constructor() : ViewModel() { private val _openItemEvent = MutableLiveData>() val openItemEvent: LiveData> = _openItemEvent - private val _openDisputeNodeEvent = MutableStateFlow(NodeItem()) - val openDisputeNodeEvent: StateFlow = _openDisputeNodeEvent + private val _openDisputeNodeEvent = MutableStateFlow(Event(NodeItem())) + val openDisputeNodeEvent: StateFlow> = _openDisputeNodeEvent private val _showNodeItemOptionsEvent = MutableLiveData>() val showNodeItemOptionsEvent: LiveData> = _showNodeItemOptionsEvent fun onItemClick(item: NodeItem) { if (item.node?.isTakenDown == true) { - _openDisputeNodeEvent.value = item + _openDisputeNodeEvent.value = Event(item) } else { _openItemEvent.value = Event(item) } diff --git a/app/src/main/java/mega/privacy/android/app/fragments/homepage/audio/AudioFragment.kt b/app/src/main/java/mega/privacy/android/app/fragments/homepage/audio/AudioFragment.kt index abd6c078514..5893800a4dc 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/homepage/audio/AudioFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/homepage/audio/AudioFragment.kt @@ -17,7 +17,6 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import mega.privacy.android.app.R import mega.privacy.android.app.arch.extensions.collectFlow @@ -164,8 +163,8 @@ class AudioFragment : Fragment(), HomepageSearchable { } lifecycleScope.launch { - itemOperationViewModel.openDisputeNodeEvent.collectLatest { - it.node?.let { node -> + itemOperationViewModel.openDisputeNodeEvent.collect { + it.getContentIfNotHandled()?.node?.let { node -> megaNodeUtilWrapper.showTakenDownDialog( isFolder = node.isFolder, context = requireContext(), diff --git a/app/src/main/java/mega/privacy/android/app/fragments/homepage/documents/DocumentsFragment.kt b/app/src/main/java/mega/privacy/android/app/fragments/homepage/documents/DocumentsFragment.kt index 959981c5fa1..041d4aba588 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/homepage/documents/DocumentsFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/homepage/documents/DocumentsFragment.kt @@ -18,7 +18,6 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import com.jeremyliao.liveeventbus.LiveEventBus import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import mega.privacy.android.app.MimeTypeList import mega.privacy.android.app.R @@ -177,9 +176,9 @@ class DocumentsFragment : Fragment(), HomepageSearchable { } } - lifecycleScope.launch { - itemOperationViewModel.openDisputeNodeEvent.collectLatest { - it.node?.let { node -> + viewLifecycleOwner.lifecycleScope.launch { + itemOperationViewModel.openDisputeNodeEvent.collect { + it.getContentIfNotHandled()?.node?.let { node -> megaNodeUtilWrapper.showTakenDownDialog( isFolder = node.isFolder, context = requireContext(), @@ -187,6 +186,7 @@ class DocumentsFragment : Fragment(), HomepageSearchable { } } } + } /** diff --git a/app/src/main/java/mega/privacy/android/app/fragments/homepage/video/VideoFragment.kt b/app/src/main/java/mega/privacy/android/app/fragments/homepage/video/VideoFragment.kt index 0d25281a937..e16cea237ef 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/homepage/video/VideoFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/homepage/video/VideoFragment.kt @@ -17,7 +17,7 @@ import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import mega.privacy.android.app.R import mega.privacy.android.app.arch.extensions.collectFlow @@ -55,6 +55,7 @@ import mega.privacy.android.app.utils.Constants.VIDEO_SEARCH_ADAPTER import mega.privacy.android.app.utils.Constants.VIEWER_FROM_VIDEOS import mega.privacy.android.app.utils.FileUtil import mega.privacy.android.app.utils.MegaApiUtils +import mega.privacy.android.app.utils.NodeTakenDownDialogListener import mega.privacy.android.app.utils.RunOnUIThreadUtils import mega.privacy.android.app.utils.TextUtil.formatEmptyScreenText import mega.privacy.android.app.utils.Util @@ -501,8 +502,8 @@ class VideoFragment : Fragment(), HomepageSearchable { }) lifecycleScope.launch { - itemOperationViewModel.openDisputeNodeEvent.collectLatest { - it.node?.let { node -> + itemOperationViewModel.openDisputeNodeEvent.collect { + it.getContentIfNotHandled()?.node?.let { node -> megaNodeUtilWrapper.showTakenDownDialog( isFolder = node.isFolder, context = requireContext(), From 04136c03bac168324ad2206cf4d28503de624332 Mon Sep 17 00:00:00 2001 From: Joe Ji Date: Tue, 6 Jun 2023 23:21:15 +1200 Subject: [PATCH 319/334] Update prebuild SDK and MEGAchat submodules --- build.gradle | 2 +- sdk/src/main/jni/megachat/sdk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 60fdb2fc1e0..dde4294ef07 100644 --- a/build.gradle +++ b/build.gradle @@ -55,7 +55,7 @@ ext { buildToolsVerion = '33.0.1' // Prebuilt MEGA SDK version - megaSdkVersion = '20230601.013830-rel' + megaSdkVersion = '20230606.094618-rel' //JDK and Java Version jdk = "17" diff --git a/sdk/src/main/jni/megachat/sdk b/sdk/src/main/jni/megachat/sdk index a511fd16b35..a57fd327dd7 160000 --- a/sdk/src/main/jni/megachat/sdk +++ b/sdk/src/main/jni/megachat/sdk @@ -1 +1 @@ -Subproject commit a511fd16b35d96a334775292b3092135326ca3d0 +Subproject commit a57fd327dd777e491dd47d9bcdb174244258c797 From 96b6410b6c46993c10fb179c1d8a9da6f658980a Mon Sep 17 00:00:00 2001 From: Kevin Gozali Date: Wed, 7 Jun 2023 04:46:11 +1200 Subject: [PATCH 320/334] AND-16166 Fix Refactor MyAccountFragment (cherry picked from commit 59f6a16633ad998a2e3d20f16adf6793221d7818) --- .../myaccount/view/MyAccountHomeView.kt | 178 +++++++++--------- .../myaccount/MyAccountHomeViewTest.kt | 66 +++++++ 2 files changed, 151 insertions(+), 93 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/presentation/myaccount/view/MyAccountHomeView.kt b/app/src/main/java/mega/privacy/android/app/presentation/myaccount/view/MyAccountHomeView.kt index bad0b5ce251..52ef5e60f7f 100644 --- a/app/src/main/java/mega/privacy/android/app/presentation/myaccount/view/MyAccountHomeView.kt +++ b/app/src/main/java/mega/privacy/android/app/presentation/myaccount/view/MyAccountHomeView.kt @@ -4,9 +4,9 @@ import android.content.res.Configuration import android.graphics.BitmapFactory import androidx.annotation.DrawableRes import androidx.annotation.StringRes -import androidx.compose.animation.animateContentSize -import androidx.compose.animation.core.LinearOutSlowInEasing -import androidx.compose.animation.core.tween +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +import androidx.compose.animation.shrinkVertically import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -64,12 +64,10 @@ import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.compose.ui.viewinterop.AndroidView import androidx.constraintlayout.compose.ChainStyle import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import androidx.navigation.NavController -import com.google.android.material.textview.MaterialTextView import de.palm.composestateevents.EventEffect import mega.privacy.android.app.R import mega.privacy.android.app.presentation.avatar.model.PhotoAvatarContent @@ -83,8 +81,6 @@ import mega.privacy.android.app.presentation.myaccount.view.Constants.ACCOUNT_TY import mega.privacy.android.app.presentation.myaccount.view.Constants.ACCOUNT_TYPE_TOP_PADDING import mega.privacy.android.app.presentation.myaccount.view.Constants.ACHIEVEMENTS import mega.privacy.android.app.presentation.myaccount.view.Constants.ADD_PHONE_NUMBER -import mega.privacy.android.app.presentation.myaccount.view.Constants.ANIMATION_DELAY -import mega.privacy.android.app.presentation.myaccount.view.Constants.ANIMATION_DURATION import mega.privacy.android.app.presentation.myaccount.view.Constants.AVATAR_SIZE import mega.privacy.android.app.presentation.myaccount.view.Constants.BACKUP_RECOVERY_KEY import mega.privacy.android.app.presentation.myaccount.view.Constants.CLICKS_TO_CHANGE_API_SERVER @@ -99,7 +95,7 @@ import mega.privacy.android.app.presentation.myaccount.view.Constants.NAME_TEXT import mega.privacy.android.app.presentation.myaccount.view.Constants.PAYMENT_ALERT_INFO import mega.privacy.android.app.presentation.myaccount.view.Constants.PHONE_NUMBER_TEXT import mega.privacy.android.app.presentation.myaccount.view.Constants.TEXT_AVATAR -import mega.privacy.android.app.presentation.myaccount.view.Constants.TIME_TO_SHOW_PAYMENT_INFO +import mega.privacy.android.app.presentation.myaccount.view.Constants.TIME_TO_SHOW_PAYMENT_INFO_IN_SECONDS import mega.privacy.android.app.presentation.myaccount.view.Constants.TOOLBAR_HEIGHT import mega.privacy.android.app.presentation.myaccount.view.Constants.UPGRADE_BUTTON import mega.privacy.android.app.presentation.myaccount.view.Constants.USAGE_METER @@ -109,15 +105,14 @@ import mega.privacy.android.app.presentation.myaccount.view.Constants.USAGE_STOR import mega.privacy.android.app.presentation.myaccount.view.Constants.USAGE_TRANSFER_IMAGE import mega.privacy.android.app.presentation.myaccount.view.Constants.USAGE_TRANSFER_PROGRESS import mega.privacy.android.app.presentation.myaccount.view.Constants.USAGE_TRANSFER_SECTION -import mega.privacy.android.app.utils.StringUtils.toSpannedHtmlText -import mega.privacy.android.app.utils.StyleUtils.setTextStyle import mega.privacy.android.app.utils.TimeUtils import mega.privacy.android.app.utils.Util +import mega.privacy.android.core.ui.controls.MegaSpannedText +import mega.privacy.android.core.ui.model.SpanIndicator import mega.privacy.android.core.ui.theme.AndroidTheme import mega.privacy.android.core.ui.theme.amber_400 import mega.privacy.android.core.ui.theme.black import mega.privacy.android.core.ui.theme.extensions.body2medium -import mega.privacy.android.core.ui.theme.extensions.conditional import mega.privacy.android.core.ui.theme.extensions.grey_020_black import mega.privacy.android.core.ui.theme.extensions.grey_050_grey_700 import mega.privacy.android.core.ui.theme.extensions.grey_050_grey_900 @@ -126,6 +121,7 @@ import mega.privacy.android.core.ui.theme.extensions.grey_alpha_012_white_alpha_ import mega.privacy.android.core.ui.theme.extensions.red_600_red_300 import mega.privacy.android.core.ui.theme.extensions.teal_300_teal_200 import mega.privacy.android.core.ui.theme.extensions.textColorSecondary +import mega.privacy.android.core.ui.theme.extensions.white_alpha_087_grey_alpha_087 import mega.privacy.android.core.ui.theme.extensions.white_black import mega.privacy.android.core.ui.theme.extensions.white_grey_800 import mega.privacy.android.core.ui.theme.red_400 @@ -138,9 +134,8 @@ import java.io.File internal object Constants { const val AVATAR_SIZE = 60 const val CLICKS_TO_CHANGE_API_SERVER = 5 - const val TIME_TO_SHOW_PAYMENT_INFO = 604800 - const val ANIMATION_DURATION = 200 - const val ANIMATION_DELAY = 500 + const val TIME_TO_SHOW_PAYMENT_INFO_IN_SECONDS = 604800 + const val ANIMATION_DURATION = 1000 val TOOLBAR_HEIGHT = 56.dp val HEADER_TOP_PADDING = 24.dp val ACCOUNT_TYPE_TOP_PADDING = 48.dp @@ -257,27 +252,30 @@ fun MyAccountHomeView( } ) - when { - uiState.isMasterBusinessAccount && uiState.isBusinessStatusActive.not() -> { - ExpiredOrGraceBusinessInfo( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), - businessStatus = uiState.businessStatus, - ) - } + if (shouldShowPaymentInfo(uiState)) { + when { + uiState.isMasterBusinessAccount && uiState.isBusinessStatusActive.not() -> { + ExpiredOrGraceBusinessInfo( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + businessStatus = uiState.businessStatus, + backgroundColor = uiState.accountType.toAccountAttributes().background + ) + } - (uiState.isMasterBusinessAccount && uiState.isBusinessStatusActive) || uiState.isBusinessAccount.not() -> { - PaymentAlertSection( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), - renewTime = uiState.subscriptionRenewTime, - expirationTime = uiState.proExpirationTime, - hasRenewableSubscription = uiState.hasRenewableSubscription, - hasExpiringSubscription = uiState.hasExpireAbleSubscription, - withAnimation = shouldExpandPaymentInfo(uiState) - ) + (uiState.isMasterBusinessAccount && uiState.isBusinessStatusActive) || uiState.isBusinessAccount.not() -> { + PaymentAlertSection( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + renewTime = uiState.subscriptionRenewTime, + expirationTime = uiState.proExpirationTime, + hasRenewableSubscription = uiState.hasRenewableSubscription, + hasExpiringSubscription = uiState.hasExpireAbleSubscription, + backgroundColor = uiState.accountType.toAccountAttributes().background + ) + } } } @@ -505,33 +503,34 @@ internal fun AccountTypeSection( internal fun ExpiredOrGraceBusinessInfo( modifier: Modifier = Modifier, businessStatus: BusinessAccountStatus?, + backgroundColor: Color, ) { - Box( - modifier = modifier - .testTag(EXPIRED_BUSINESS_BANNER) - .padding(horizontal = 22.dp) - .animateContentSize( - animationSpec = tween( - durationMillis = ANIMATION_DURATION, - delayMillis = ANIMATION_DELAY, - easing = LinearOutSlowInEasing - ) - ) + AnimatedVisibility( + visible = true, + enter = expandVertically(), + exit = shrinkVertically() ) { - val businessExpiryText = - if (businessStatus == BusinessAccountStatus.Expired) R.string.payment_overdue_label else R.string.payment_required_label - val businessTextColor = - if (businessStatus == BusinessAccountStatus.Expired) red_400 else amber_400 + Box( + modifier = modifier + .testTag(EXPIRED_BUSINESS_BANNER) + .padding(horizontal = 22.dp) + .background(backgroundColor) + ) { + val businessExpiryText = + if (businessStatus == BusinessAccountStatus.Expired) R.string.payment_overdue_label else R.string.payment_required_label + val businessTextColor = + if (businessStatus == BusinessAccountStatus.Expired) red_400 else amber_400 - Text( - modifier = Modifier - .testTag(EXPIRED_BUSINESS_BANNER_TEXT) - .fillMaxWidth() - .padding(start = 12.dp, bottom = 10.dp), - text = stringResource(id = businessExpiryText), - style = MaterialTheme.typography.body2, - color = businessTextColor - ) + Text( + modifier = Modifier + .testTag(EXPIRED_BUSINESS_BANNER_TEXT) + .fillMaxWidth() + .padding(start = 12.dp, bottom = 10.dp), + text = stringResource(id = businessExpiryText), + style = MaterialTheme.typography.body2, + color = businessTextColor + ) + } } } @@ -542,49 +541,42 @@ private fun PaymentAlertSection( expirationTime: Long, hasRenewableSubscription: Boolean, hasExpiringSubscription: Boolean, - withAnimation: Boolean, + backgroundColor: Color, ) { - if (hasRenewableSubscription || hasExpiringSubscription) { + AnimatedVisibility( + visible = hasRenewableSubscription || hasExpiringSubscription, + enter = expandVertically(), + exit = shrinkVertically() + ) { Box( modifier = modifier .testTag(PAYMENT_ALERT_INFO) .padding(horizontal = 22.dp) - .conditional(withAnimation) { - animateContentSize( - animationSpec = tween( - durationMillis = ANIMATION_DURATION, - delayMillis = ANIMATION_DELAY, - easing = LinearOutSlowInEasing - ) - ) - } + .background(backgroundColor) ) { - val renewText = stringResource( - if (hasRenewableSubscription) R.string.account_info_renews_on else R.string.account_info_expires_on, - TimeUtils.formatDate( - if (hasRenewableSubscription) renewTime else expirationTime, - TimeUtils.DATE_MM_DD_YYYY_FORMAT, - LocalContext.current - ) - ).replace("[A]", "") - .replace("[/A]", "") - .replace("[B]", "") - .replace("[/B]", "") - .toSpannedHtmlText() - - AndroidView( + MegaSpannedText( modifier = Modifier .fillMaxWidth() .padding(start = 12.dp, bottom = 10.dp), - factory = { - MaterialTextView(it) - }, - update = { - it.text = renewText - it.setTextStyle( - textAppearance = R.style.TextAppearance_Mega_Subtitle2_Normal_Grey54White54, + value = stringResource( + if (hasRenewableSubscription) R.string.account_info_renews_on else R.string.account_info_expires_on, + TimeUtils.formatDate( + if (hasRenewableSubscription) renewTime else expirationTime, + TimeUtils.DATE_MM_DD_YYYY_FORMAT, + LocalContext.current ) - } + ), + baseStyle = MaterialTheme.typography.subtitle2.copy(color = MaterialTheme.colors.white_alpha_087_grey_alpha_087), + styles = hashMapOf( + SpanIndicator('A') to SpanStyle( + fontSize = 12.sp, + fontWeight = FontWeight.Normal + ), + SpanIndicator('B') to SpanStyle( + fontSize = 14.sp, + fontWeight = FontWeight.Medium + ) + ) ) } } @@ -999,12 +991,12 @@ private fun AccountInfoListItem( @Composable private fun formatSize(size: Long): String = Util.getSizeString(size, LocalContext.current) -private fun shouldExpandPaymentInfo(uiState: MyAccountHomeUIState): Boolean { +private fun shouldShowPaymentInfo(uiState: MyAccountHomeUIState): Boolean { val timeToCheck = if (uiState.hasRenewableSubscription) uiState.subscriptionRenewTime else uiState.proExpirationTime val currentTime = System.currentTimeMillis() / 1000 - return timeToCheck.minus(currentTime) <= TIME_TO_SHOW_PAYMENT_INFO + return timeToCheck.minus(currentTime) <= TIME_TO_SHOW_PAYMENT_INFO_IN_SECONDS } @Preview diff --git a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/myaccount/MyAccountHomeViewTest.kt b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/myaccount/MyAccountHomeViewTest.kt index ff80434ede6..b7d2e9e70fe 100644 --- a/app/src/testDebug/java/test/mega/privacy/android/app/presentation/myaccount/MyAccountHomeViewTest.kt +++ b/app/src/testDebug/java/test/mega/privacy/android/app/presentation/myaccount/MyAccountHomeViewTest.kt @@ -446,6 +446,72 @@ class MyAccountHomeViewTest { .assert(hasText(fromId(R.string.sms_add_phone_number_dialog_msg_non_achievement_user))) } + @Test + fun `test that expire or grace alert should not be shown when due is more than 7 days`() { + val eightDaysInSeconds = 691200 + val dueDateInSeconds = (System.currentTimeMillis() / 1000) + eightDaysInSeconds + + initMyAccountWithDefaults( + MyAccountHomeUIState( + isMasterBusinessAccount = true, + isBusinessStatusActive = false, + hasRenewableSubscription = true, + subscriptionRenewTime = dueDateInSeconds + ) + ) + + composeTestRule.onNodeWithTag(EXPIRED_BUSINESS_BANNER).assertDoesNotExist() + } + + @Test + fun `test that expire or grace alert should be shown when due is less than 7 days`() { + val fiveDaysInSeconds = 432000 + val dueDateInSeconds = (System.currentTimeMillis() / 1000) + fiveDaysInSeconds + + initMyAccountWithDefaults( + MyAccountHomeUIState( + isMasterBusinessAccount = true, + isBusinessStatusActive = false, + hasRenewableSubscription = true, + subscriptionRenewTime = dueDateInSeconds + ) + ) + + composeTestRule.onNodeWithTag(EXPIRED_BUSINESS_BANNER).assertIsDisplayed() + } + + @Test + fun `test that payment alert should not be shown when due is more than 7 days`() { + val eightDaysInSeconds = 691200 + val dueDateInSeconds = (System.currentTimeMillis() / 1000) + eightDaysInSeconds + + initMyAccountWithDefaults( + MyAccountHomeUIState( + isBusinessAccount = false, + hasRenewableSubscription = true, + subscriptionRenewTime = dueDateInSeconds + ) + ) + + composeTestRule.onNodeWithTag(PAYMENT_ALERT_INFO).assertDoesNotExist() + } + + @Test + fun `test that payment alert should be shown when due is less than 7 days`() { + val fiveDaysInSeconds = 432000 + val dueDateInSeconds = (System.currentTimeMillis() / 1000) + fiveDaysInSeconds + + initMyAccountWithDefaults( + MyAccountHomeUIState( + isBusinessAccount = false, + hasRenewableSubscription = true, + subscriptionRenewTime = dueDateInSeconds + ) + ) + + composeTestRule.onNodeWithTag(PAYMENT_ALERT_INFO).assertIsDisplayed() + } + private fun verifyAccountTypeSectionColor( accountType: AccountType, color: Color, From 8e2756c40cbaf535a23ed78fe5b0600ae64947a4 Mon Sep 17 00:00:00 2001 From: Rohit Soni Date: Wed, 7 Jun 2023 20:02:45 +1200 Subject: [PATCH 321/334] FM-288 Automatic Link copy --- .../mega/privacy/android/app/getLink/GetLinkFragment.kt | 4 ---- .../mega/privacy/android/app/getLink/GetLinkViewModel.kt | 7 ++++--- .../privacy/android/app/getLink/GetSeveralLinksFragment.kt | 2 ++ app/src/main/res/layout/fragment_get_several_links.xml | 4 ++-- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/mega/privacy/android/app/getLink/GetLinkFragment.kt b/app/src/main/java/mega/privacy/android/app/getLink/GetLinkFragment.kt index 0f317142784..232448480d1 100644 --- a/app/src/main/java/mega/privacy/android/app/getLink/GetLinkFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/getLink/GetLinkFragment.kt @@ -174,10 +174,6 @@ class GetLinkFragment : Fragment(), DatePickerDialog.OnDateSetListener, Scrollab binding.passwordProtectionProOnlyText.isVisible = true } - if (node?.isExported == false) { - viewModel.export() - } - binding.scrollViewGetLink.postDelayed(::checkScroll, POST_CHECK_SCROLL) } diff --git a/app/src/main/java/mega/privacy/android/app/getLink/GetLinkViewModel.kt b/app/src/main/java/mega/privacy/android/app/getLink/GetLinkViewModel.kt index f88a1903ad4..7248d6b743f 100644 --- a/app/src/main/java/mega/privacy/android/app/getLink/GetLinkViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/getLink/GetLinkViewModel.kt @@ -132,6 +132,7 @@ class GetLinkViewModel @Inject constructor( onError = Timber::w ) .addTo(composite) + } /** @@ -258,10 +259,10 @@ class GetLinkViewModel @Inject constructor( val link = node?.publicLink linkWithoutKey = LinksUtil.getLinkWithoutKey(link) key = LinksUtil.getKeyLink(link) + updateLink() + } else { + export() } - - updateLink() - expiryDate.value = if ((node?.expirationTime ?: 0) > 0) getExpiredDateText() else "" } diff --git a/app/src/main/java/mega/privacy/android/app/getLink/GetSeveralLinksFragment.kt b/app/src/main/java/mega/privacy/android/app/getLink/GetSeveralLinksFragment.kt index 8b66e0f62a7..57d93124e7c 100644 --- a/app/src/main/java/mega/privacy/android/app/getLink/GetSeveralLinksFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/getLink/GetSeveralLinksFragment.kt @@ -123,6 +123,8 @@ class GetSeveralLinksFragment : Fragment() { ) } + copyLinks(viewModel.getLinksString()) + binding.copyButton.apply { isEnabled = false setOnClickListener { copyLinks(viewModel.getLinksString()) } diff --git a/app/src/main/res/layout/fragment_get_several_links.xml b/app/src/main/res/layout/fragment_get_several_links.xml index d4544ed4f39..4fd30a24786 100644 --- a/app/src/main/res/layout/fragment_get_several_links.xml +++ b/app/src/main/res/layout/fragment_get_several_links.xml @@ -40,10 +40,10 @@