diff --git a/app/src/main/java/mega/privacy/android/app/BaseActivity.java b/app/src/main/java/mega/privacy/android/app/BaseActivity.java index 2c62fcfc7b8..01100cffbcb 100644 --- a/app/src/main/java/mega/privacy/android/app/BaseActivity.java +++ b/app/src/main/java/mega/privacy/android/app/BaseActivity.java @@ -48,9 +48,11 @@ import dagger.hilt.android.AndroidEntryPoint; import kotlin.Pair; import kotlin.Suppress; +import mega.privacy.android.app.components.saver.AutoPlayInfo; import mega.privacy.android.app.globalmanagement.MyAccountInfo; import mega.privacy.android.app.interfaces.ActivityLauncher; import mega.privacy.android.app.interfaces.PermissionRequester; +import mega.privacy.android.app.interfaces.SnackbarShower; import mega.privacy.android.app.listeners.ChatLogoutListener; import mega.privacy.android.app.lollipop.LoginActivityLollipop; import mega.privacy.android.app.lollipop.ManagerActivityLollipop; @@ -65,6 +67,7 @@ import mega.privacy.android.app.service.iar.RatingHandlerImpl; import mega.privacy.android.app.smsVerification.SMSVerificationActivity; import mega.privacy.android.app.snackbarListeners.SnackbarNavigateOption; +import mega.privacy.android.app.utils.MegaNodeUtil; import mega.privacy.android.app.utils.PermissionUtils; import mega.privacy.android.app.utils.ColorUtils; import mega.privacy.android.app.utils.StringResourcesUtils; @@ -96,7 +99,7 @@ import java.util.List; @AndroidEntryPoint -public class BaseActivity extends AppCompatActivity implements ActivityLauncher, PermissionRequester, +public class BaseActivity extends AppCompatActivity implements ActivityLauncher, PermissionRequester, SnackbarShower, BillingUpdatesListener { private static final String EXPIRED_BUSINESS_ALERT_SHOWN = "EXPIRED_BUSINESS_ALERT_SHOWN"; @@ -132,6 +135,11 @@ public class BaseActivity extends AppCompatActivity implements ActivityLauncher, private AlertDialog transferGeneralOverQuotaWarning; private Snackbar snackbar; + /** + * Contains the info of a node that to be opened in-app. + */ + private AutoPlayInfo autoPlayInfo; + /** * Load the psa in the web browser fragment if the psa is a web one and this activity * is on the top of the task stack @@ -463,23 +471,37 @@ public void onReceive(Context context, Intent intent) { return; } - String message = null; int numTransfers = intent.getIntExtra(NUMBER_FILES, 1); + String message = getResources().getQuantityString(R.plurals.download_finish, numTransfers, numTransfers); switch (intent.getStringExtra(TRANSFER_TYPE)) { case DOWNLOAD_TRANSFER: - message = getResources().getQuantityString(R.plurals.download_finish, numTransfers, numTransfers); + Util.showSnackbar(baseActivity, message); break; case UPLOAD_TRANSFER: message = getResources().getQuantityString(R.plurals.upload_finish, numTransfers, numTransfers); + Util.showSnackbar(baseActivity, message); break; - } - Util.showSnackbar(baseActivity, message); + case DOWNLOAD_TRANSFER_OPEN: + autoPlayInfo = new AutoPlayInfo(intent.getStringExtra(NODE_NAME), intent.getLongExtra(NODE_HANDLE, INVALID_VALUE), intent.getStringExtra(NODE_LOCAL_PATH), true); + showSnackbar(OPEN_FILE_SNACKBAR_TYPE, message, MEGACHAT_INVALID_HANDLE); + break; + } } }; + /** + * Open the downloaded file. + */ + private void openDownloadedFile() { + if(autoPlayInfo != null) { + MegaNodeUtil.autoPlayNode(BaseActivity.this, autoPlayInfo, BaseActivity.this,BaseActivity.this); + autoPlayInfo = null; + } + } + private BroadcastReceiver showSnackbarReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -819,6 +841,12 @@ public void showSnackbar (int type, View view, View anchor, String s, long idCha snackbar.setAction(R.string.general_ok, new SnackbarNavigateOption(view.getContext(), type)); snackbar.show(); break; + + case OPEN_FILE_SNACKBAR_TYPE: { + snackbar.setAction(R.string.action_see, (v) -> openDownloadedFile()); + snackbar.show(); + break; + } } } @@ -1376,4 +1404,10 @@ public void onQueryPurchasesFinished(boolean isFailed, int resultCode, List 52) { - ChatSettings chatSettings = getChatSettingsFromDBv62(db); - db.execSQL("DROP TABLE IF EXISTS " + TABLE_CHAT_SETTINGS); - onCreate(db); - setChatSettings(db, chatSettings); - - // Temporary fix to avoid wrong values in chat settings after upgrade. - getChatSettings(db); - } - - MegaPreferences preferences = getPreferencesFromDBv62(db); - db.execSQL("DROP TABLE IF EXISTS " + TABLE_PREFERENCES); - onCreate(db); - - if (preferences != null) { - setPreferences(db, preferences); + if (!chatSettingsAlreadyUpdated) { + recreateChatSettings(db, getChatSettingsFromDBv62(db)); + chatSettingsAlreadyUpdated = true; } - // After re-create the table, refresh it to make sure the columns have correct index. - getPreferences(db); - + recreatePreferences(db, getPreferencesFromDBv62(db)); preferencesAlreadyUpdated = true; } @@ -893,18 +874,76 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("UPDATE " + TABLE_PREFERENCES + " SET " + KEY_FINGERPRINT_LOCK + " = '" + encrypt("false") + "';"); } - if (oldVersion <= 64) { - MegaPreferences preferences = getPreferences(db); - db.execSQL("DROP TABLE IF EXISTS " + TABLE_PREFERENCES); - onCreate(db); + if (oldVersion <= 64 && !preferencesAlreadyUpdated) { + //KEY_CAM_SYNC_CHARGING and KEY_SMALL_GRID_CAMERA have been removed in DB v64 + recreatePreferences(db, getPreferences(db)); + preferencesAlreadyUpdated = true; + } - if (preferences != null) { - setPreferences(db, preferences); - } + if (oldVersion <= 65 && !preferencesAlreadyUpdated) { + //KEY_PREFERRED_SORT_CONTACTS has been removed in DB v65 + recreatePreferences(db, getPreferences(db)); + preferencesAlreadyUpdated = true; + } - // After re-create the table, refresh it to make sure the columns have correct index. - getPreferences(db); - } + this.db = db; + } + + /** + * Drops the chat settings table if exists, creates the new one, + * and then sets the updated chat settings. + * + * @param db Current DB. + * @param chatSettings Chat Settings. + */ + private void recreateChatSettings(SQLiteDatabase db, ChatSettings chatSettings) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_CHAT_SETTINGS); + onCreate(db); + + if (chatSettings != null) { + setChatSettings(db, chatSettings); + } + + // Temporary fix to avoid wrong values in chat settings after upgrade. + getChatSettings(db); + } + + /** + * Drops the attributes table if exists, creates the new one, + * and then sets the updated attributes. + * + * @param db Current DB. + * @param attr Attributes. + */ + private void recreateAttributes(SQLiteDatabase db, MegaAttributes attr) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_ATTRIBUTES); + onCreate(db); + + if (attr != null) { + setAttributes(db, attr); + } + + // Temporary fix to avoid wrong values in attributes after upgrade. + getAttributes(db); + } + + /** + * Drops the preferences table if exists, creates the new one, + * and then sets the updated preferences. + * + * @param db Current DB. + * @param preferences Preferences. + */ + private void recreatePreferences(SQLiteDatabase db, MegaPreferences preferences) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE_PREFERENCES); + onCreate(db); + + if (preferences != null) { + setPreferences(db, preferences); + } + + // Temporary fix to avoid wrong values in preferences after upgrade. + getPreferences(db); } public static String encrypt(String original) { @@ -1552,7 +1591,6 @@ private void setPreferences (SQLiteDatabase db, MegaPreferences prefs){ values.put(KEY_CAMERA_FOLDER_EXTERNAL_SD_CARD, encrypt(prefs.getCameraFolderExternalSDCard())); values.put(KEY_PASSCODE_LOCK_TYPE, encrypt(prefs.getPasscodeLockType())); values.put(KEY_PREFERRED_SORT_CLOUD, encrypt(prefs.getPreferredSortCloud())); - values.put(KEY_PREFERRED_SORT_CONTACTS, encrypt(prefs.getPreferredSortContacts())); values.put(KEY_PREFERRED_SORT_CAMERA_UPLOAD, encrypt(prefs.getPreferredSortCameraUpload())); values.put(KEY_PREFERRED_SORT_OTHERS, encrypt(prefs.getPreferredSortOthers())); values.put(KEY_FIRST_LOGIN_CHAT, encrypt(prefs.getFirstTimeChat())); @@ -1629,56 +1667,54 @@ private MegaPreferences getPreferences(SQLiteDatabase db) { MegaPreferences prefs = null; String selectQuery = "SELECT * FROM " + TABLE_PREFERENCES; - try (Cursor cursor = db.rawQuery(selectQuery, null)) { - if (cursor != null && cursor.moveToFirst()) { - String firstTime = decrypt(cursor.getString(getColumnIndex(cursor, KEY_FIRST_LOGIN))); - String camSyncEnabled = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_SYNC_ENABLED))); - String camSyncHandle = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_SYNC_HANDLE))); - String camSyncLocalPath = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_SYNC_LOCAL_PATH))); - String wifi = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_SYNC_WIFI))); - String fileUpload = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_SYNC_FILE_UPLOAD))); - String pinLockEnabled = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PASSCODE_LOCK_ENABLED))); - String pinLockCode = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PASSCODE_LOCK_CODE))); - String askAlways = decrypt(cursor.getString(getColumnIndex(cursor, KEY_STORAGE_ASK_ALWAYS))); - String downloadLocation = decrypt(cursor.getString(getColumnIndex(cursor, KEY_STORAGE_DOWNLOAD_LOCATION))); - String camSyncTimeStamp = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_SYNC_TIMESTAMP))); - String lastFolderUpload = decrypt(cursor.getString(getColumnIndex(cursor, KEY_LAST_UPLOAD_FOLDER))); - String lastFolderCloud = decrypt(cursor.getString(getColumnIndex(cursor, KEY_LAST_CLOUD_FOLDER_HANDLE))); - String secondaryFolderEnabled = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SEC_FOLDER_ENABLED))); - String secondaryPath = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SEC_FOLDER_LOCAL_PATH))); - String secondaryHandle = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SEC_FOLDER_HANDLE))); - String secSyncTimeStamp = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SEC_SYNC_TIMESTAMP))); - String keepFileNames = decrypt(cursor.getString(getColumnIndex(cursor, KEY_KEEP_FILE_NAMES))); - String storageAdvancedDevices = decrypt(cursor.getString(getColumnIndex(cursor, KEY_STORAGE_ADVANCED_DEVICES))); - String preferredViewList = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PREFERRED_VIEW_LIST))); - String preferredViewListCamera = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PREFERRED_VIEW_LIST_CAMERA))); - String uriExternalSDCard = decrypt(cursor.getString(getColumnIndex(cursor, KEY_URI_EXTERNAL_SD_CARD))); - String cameraFolderExternalSDCard = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAMERA_FOLDER_EXTERNAL_SD_CARD))); - String pinLockType = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PASSCODE_LOCK_TYPE))); - String preferredSortCloud = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PREFERRED_SORT_CLOUD))); - String preferredSortContacts = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PREFERRED_SORT_CONTACTS))); - String preferredSortOthers = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PREFERRED_SORT_OTHERS))); - String firstTimeChat = decrypt(cursor.getString(getColumnIndex(cursor, KEY_FIRST_LOGIN_CHAT))); - String isAutoPlayEnabled = decrypt(cursor.getString(getColumnIndex(cursor, KEY_AUTO_PLAY))); - String uploadVideoQuality = decrypt(cursor.getString(getColumnIndex(cursor, KEY_UPLOAD_VIDEO_QUALITY))); - String conversionOnCharging = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CONVERSION_ON_CHARGING))); - String chargingOnSize = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CHARGING_ON_SIZE))); - String shouldClearCameraSyncRecords = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SHOULD_CLEAR_CAMSYNC_RECORDS))); - String camVideoSyncTimeStamp = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_VIDEO_SYNC_TIMESTAMP))); - String secVideoSyncTimeStamp = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SEC_VIDEO_SYNC_TIMESTAMP))); - String removeGPS = decrypt(cursor.getString(getColumnIndex(cursor, KEY_REMOVE_GPS))); - String closeInviteBanner = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SHOW_INVITE_BANNER))); - String preferredSortCameraUpload = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PREFERRED_SORT_CAMERA_UPLOAD))); - String sdCardUri = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SD_CARD_URI))); - String askForDisplayOver = decrypt(cursor.getString(getColumnIndex(cursor, KEY_ASK_FOR_DISPLAY_OVER))); - String askForSetDownloadLocation = decrypt(cursor.getString(getColumnIndex(cursor, KEY_ASK_SET_DOWNLOAD_LOCATION))); - String mediaSDCardUri = decrypt(cursor.getString(getColumnIndex(cursor, KEY_URI_MEDIA_EXTERNAL_SD_CARD))); - String isMediaOnSDCard = decrypt(cursor.getString(getColumnIndex(cursor, KEY_MEDIA_FOLDER_EXTERNAL_SD_CARD))); - String passcodeLockRequireTime = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PASSCODE_LOCK_REQUIRE_TIME))); - - int fingerprintLockIndex = getColumnIndex(cursor, KEY_FINGERPRINT_LOCK); - String fingerprintLock = fingerprintLockIndex != INVALID_VALUE - ? decrypt(cursor.getString(fingerprintLockIndex)) : "false"; + try (Cursor cursor = db.rawQuery(selectQuery, null)) { + if (cursor != null && cursor.moveToFirst()) { + String firstTime = decrypt(cursor.getString(getColumnIndex(cursor, KEY_FIRST_LOGIN))); + String camSyncEnabled = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_SYNC_ENABLED))); + String camSyncHandle = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_SYNC_HANDLE))); + String camSyncLocalPath = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_SYNC_LOCAL_PATH))); + String wifi = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_SYNC_WIFI))); + String fileUpload = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_SYNC_FILE_UPLOAD))); + String pinLockEnabled = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PASSCODE_LOCK_ENABLED))); + String pinLockCode = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PASSCODE_LOCK_CODE))); + String askAlways = decrypt(cursor.getString(getColumnIndex(cursor, KEY_STORAGE_ASK_ALWAYS))); + String downloadLocation = decrypt(cursor.getString(getColumnIndex(cursor, KEY_STORAGE_DOWNLOAD_LOCATION))); + String camSyncTimeStamp = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_SYNC_TIMESTAMP))); + String lastFolderUpload = decrypt(cursor.getString(getColumnIndex(cursor, KEY_LAST_UPLOAD_FOLDER))); + String lastFolderCloud = decrypt(cursor.getString(getColumnIndex(cursor, KEY_LAST_CLOUD_FOLDER_HANDLE))); + String secondaryFolderEnabled = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SEC_FOLDER_ENABLED))); + String secondaryPath = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SEC_FOLDER_LOCAL_PATH))); + String secondaryHandle = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SEC_FOLDER_HANDLE))); + String secSyncTimeStamp = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SEC_SYNC_TIMESTAMP))); + String keepFileNames = decrypt(cursor.getString(getColumnIndex(cursor, KEY_KEEP_FILE_NAMES))); + String storageAdvancedDevices = decrypt(cursor.getString(getColumnIndex(cursor, KEY_STORAGE_ADVANCED_DEVICES))); + String preferredViewList = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PREFERRED_VIEW_LIST))); + String preferredViewListCamera = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PREFERRED_VIEW_LIST_CAMERA))); + String uriExternalSDCard = decrypt(cursor.getString(getColumnIndex(cursor, KEY_URI_EXTERNAL_SD_CARD))); + String cameraFolderExternalSDCard = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAMERA_FOLDER_EXTERNAL_SD_CARD))); + String pinLockType = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PASSCODE_LOCK_TYPE))); + String preferredSortCloud = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PREFERRED_SORT_CLOUD))); + String preferredSortOthers = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PREFERRED_SORT_OTHERS))); + String firstTimeChat = decrypt(cursor.getString(getColumnIndex(cursor, KEY_FIRST_LOGIN_CHAT))); + String isAutoPlayEnabled = decrypt(cursor.getString(getColumnIndex(cursor, KEY_AUTO_PLAY))); + String uploadVideoQuality = decrypt(cursor.getString(getColumnIndex(cursor, KEY_UPLOAD_VIDEO_QUALITY))); + String conversionOnCharging = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CONVERSION_ON_CHARGING))); + String chargingOnSize = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CHARGING_ON_SIZE))); + String shouldClearCameraSyncRecords = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SHOULD_CLEAR_CAMSYNC_RECORDS))); + String camVideoSyncTimeStamp = decrypt(cursor.getString(getColumnIndex(cursor, KEY_CAM_VIDEO_SYNC_TIMESTAMP))); + String secVideoSyncTimeStamp = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SEC_VIDEO_SYNC_TIMESTAMP))); + String removeGPS = decrypt(cursor.getString(getColumnIndex(cursor, KEY_REMOVE_GPS))); + String closeInviteBanner = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SHOW_INVITE_BANNER))); + String preferredSortCameraUpload = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PREFERRED_SORT_CAMERA_UPLOAD))); + String sdCardUri = decrypt(cursor.getString(getColumnIndex(cursor, KEY_SD_CARD_URI))); + String askForDisplayOver = decrypt(cursor.getString(getColumnIndex(cursor, KEY_ASK_FOR_DISPLAY_OVER))); + String askForSetDownloadLocation = decrypt(cursor.getString(getColumnIndex(cursor, KEY_ASK_SET_DOWNLOAD_LOCATION))); + String mediaSDCardUri = decrypt(cursor.getString(getColumnIndex(cursor, KEY_URI_MEDIA_EXTERNAL_SD_CARD))); + String isMediaOnSDCard = decrypt(cursor.getString(getColumnIndex(cursor, KEY_MEDIA_FOLDER_EXTERNAL_SD_CARD))); + String passcodeLockRequireTime = decrypt(cursor.getString(getColumnIndex(cursor, KEY_PASSCODE_LOCK_REQUIRE_TIME))); + String fingerprintLock = cursor.getColumnIndex(KEY_FINGERPRINT_LOCK) != INVALID_VALUE + ? decrypt(cursor.getString(getColumnIndex(cursor, KEY_FINGERPRINT_LOCK))) + : "false"; prefs = new MegaPreferences(firstTime, wifi, camSyncEnabled, camSyncHandle, camSyncLocalPath, fileUpload, camSyncTimeStamp, pinLockEnabled, @@ -1686,13 +1722,12 @@ private MegaPreferences getPreferences(SQLiteDatabase db) { lastFolderCloud, secondaryFolderEnabled, secondaryPath, secondaryHandle, secSyncTimeStamp, keepFileNames, storageAdvancedDevices, preferredViewList, preferredViewListCamera, uriExternalSDCard, cameraFolderExternalSDCard, - pinLockType, preferredSortCloud, preferredSortContacts, preferredSortOthers, - firstTimeChat, uploadVideoQuality, conversionOnCharging, - chargingOnSize, shouldClearCameraSyncRecords, camVideoSyncTimeStamp, - secVideoSyncTimeStamp, isAutoPlayEnabled, removeGPS, closeInviteBanner, - preferredSortCameraUpload, sdCardUri, askForDisplayOver, - askForSetDownloadLocation, mediaSDCardUri, isMediaOnSDCard, - passcodeLockRequireTime, fingerprintLock); + pinLockType, preferredSortCloud, preferredSortOthers, firstTimeChat, + uploadVideoQuality, conversionOnCharging, chargingOnSize, + shouldClearCameraSyncRecords, camVideoSyncTimeStamp, secVideoSyncTimeStamp, + isAutoPlayEnabled, removeGPS, closeInviteBanner, preferredSortCameraUpload, + sdCardUri, askForDisplayOver, askForSetDownloadLocation, mediaSDCardUri, + isMediaOnSDCard, passcodeLockRequireTime, fingerprintLock); } } catch (Exception e) { logError("Exception opening or managing DB cursor", e); @@ -2231,7 +2266,13 @@ private MegaAttributes getAttributesFromDBv61(SQLiteDatabase db) { return attr; } - public MegaAttributes getAttributes(){ + /** + * Gets attributes. + * + * @param db Current DB. + * @return The attributes. + */ + private MegaAttributes getAttributes(SQLiteDatabase db) { MegaAttributes attr = null; String selectQuery = "SELECT * FROM " + TABLE_ATTRIBUTES; @@ -2272,28 +2313,18 @@ public MegaAttributes getAttributes(){ } catch (Exception e) { logError("Exception opening or managing DB cursor", e); } + return attr; } -// public void setNonContact (NonContactInfo nonContact){ -// log("setNonContact: "+nonContact.getHandle()); -// -// ContentValues values = new ContentValues(); -// values.put(KEY_NONCONTACT_HANDLE, encrypt(nonContact.getHandle())); -// values.put(KEY_NONCONTACT_FULLNAME, encrypt(nonContact.getFullName())); -// values.put(KEY_NONCONTACT_FIRSTNAME, encrypt(nonContact.getFirstName())); -// values.put(KEY_NONCONTACT_LASTNAME, encrypt(nonContact.getLastName())); -// -// NonContactInfo check = findNonContactByHandle(nonContact.getHandle()+""); -// -// if(check==null){ -// db.insert(TABLE_NON_CONTACTS, null, values); -// } -// else{ -// int id = (int) db.insertWithOnConflict(TABLE_NON_CONTACTS, null, values, SQLiteDatabase.CONFLICT_REPLACE); -// log("setNonContact: Final value: "+id); -// } -// } + /** + * Gets attributes. + * + * @return The attributes. + */ + public MegaAttributes getAttributes() { + return getAttributes(db); + } public int setNonContactFirstName (String name, String handle){ logDebug("setContactName: " + name + " " + handle); @@ -2329,14 +2360,6 @@ public int setNonContactEmail (String email, String handle){ return rows; } -// public int setNonContactFullName (String fullName, String handle){ -// log("setNonContactFullName: "+fullName); -// -// ContentValues values = new ContentValues(); -// values.put(KEY_NONCONTACT_FULLNAME, encrypt(fullName)); -// return db.update(TABLE_NON_CONTACTS, values, KEY_NONCONTACT_FULLNAME + " = '" + encrypt(handle) + "'", null); -// } - public NonContactInfo findNonContactByHandle(String handle){ logDebug("findNONContactByHandle: " + handle); NonContactInfo noncontact = null; @@ -2841,23 +2864,6 @@ public void setFirstTime (boolean firstTime){ } } -// public void setFirstTimeChat (boolean firstTimeChat){ -// String selectQuery = "SELECT * FROM " + TABLE_PREFERENCES; -// ContentValues values = new ContentValues(); -// Cursor cursor = db.rawQuery(selectQuery, null); -// if (cursor.moveToFirst()){ -// String UPDATE_PREFERENCES_TABLE = "UPDATE " + TABLE_PREFERENCES + " SET " + KEY_FIRST_LOGIN_CHAT + "= '" + encrypt(firstTimeChat + "") + "' WHERE " + KEY_ID + " = '1'"; -// db.execSQL(UPDATE_PREFERENCES_TABLE); -//// log("UPDATE_PREFERENCES_TABLE: " + UPDATE_PREFERENCES_TABLE); -// } -// else{ -// values.put(KEY_FIRST_LOGIN_CHAT, encrypt(firstTimeChat + "")); -// db.insert(TABLE_PREFERENCES, null, values); -// } -// cursor.close(); -// } -// - public void setCamSyncWifi (boolean wifi){ String selectQuery = "SELECT * FROM " + TABLE_PREFERENCES; ContentValues values = new ContentValues(); @@ -2926,23 +2932,6 @@ public void setPreferredSortCloud (String order){ } } - public void setPreferredSortContacts (String order){ - String selectQuery = "SELECT * FROM " + TABLE_PREFERENCES; - ContentValues values = new ContentValues(); - try (Cursor cursor = db.rawQuery(selectQuery, null)) { - if (cursor != null && cursor.moveToFirst()) { - String UPDATE_PREFERENCES_TABLE = "UPDATE " + TABLE_PREFERENCES + " SET " + KEY_PREFERRED_SORT_CONTACTS + "= '" + encrypt(order) + "' WHERE " + KEY_ID + " = '1'"; - db.execSQL(UPDATE_PREFERENCES_TABLE); -// log("UPDATE_PREFERENCES_TABLE SYNC WIFI: " + UPDATE_PREFERENCES_TABLE); - } else { - values.put(KEY_PREFERRED_SORT_CONTACTS, encrypt(order)); - db.insert(TABLE_PREFERENCES, null, values); - } - } catch (Exception e) { - logError("Exception opening or managing DB cursor", e); - } - } - public void setPreferredSortCameraUpload(String order) { logDebug("set sort camera upload order: " + order); setStringValue(TABLE_PREFERENCES, KEY_PREFERRED_SORT_CAMERA_UPLOAD, order); @@ -2999,23 +2988,6 @@ public void setLastCloudFolder (String folderHandle){ } } - -// public void setCamSyncCharging (boolean charging){ -// String selectQuery = "SELECT * FROM " + TABLE_PREFERENCES; -// ContentValues values = new ContentValues(); -// Cursor cursor = db.rawQuery(selectQuery, null); -// if (cursor.moveToFirst()){ -// String UPDATE_PREFERENCES_TABLE = "UPDATE " + TABLE_PREFERENCES + " SET " + KEY_CAM_SYNC_CHARGING + "= '" + encrypt(charging + "") + "' WHERE " + KEY_ID + " = '1'"; -// db.execSQL(UPDATE_PREFERENCES_TABLE); -//// log("UPDATE_PREFERENCES_TABLE SYNC CHARGING: " + UPDATE_PREFERENCES_TABLE); -// } -// else{ -// values.put(KEY_CAM_SYNC_CHARGING, encrypt(charging + "")); -// db.insert(TABLE_PREFERENCES, null, values); -// } -// cursor.close(); -// } - public void setKeepFileNames (boolean charging){ String selectQuery = "SELECT * FROM " + TABLE_PREFERENCES; ContentValues values = new ContentValues(); @@ -3653,21 +3625,6 @@ public void setStorageDownloadLocation (String storageDownloadLocation){ } } -// public void setAttrOnline (boolean online){ -// String selectQuery = "SELECT * FROM " + TABLE_ATTRIBUTES; -// ContentValues values = new ContentValues(); -// Cursor cursor = db.rawQuery(selectQuery, null); -// if (cursor.moveToFirst()){ -// String UPDATE_ATTRIBUTES_TABLE = "UPDATE " + TABLE_ATTRIBUTES + " SET " + KEY_ATTR_ONLINE + "='" + encrypt(online + "") + "' WHERE " + KEY_ID + " ='1'"; -// db.execSQL(UPDATE_ATTRIBUTES_TABLE); -// } -// else{ -// values.put(KEY_ATTR_ONLINE, encrypt(online + "")); -// db.insert(TABLE_ATTRIBUTES, null, values); -// } -// cursor.close(); -// } -// public void setAttrAskSizeDownload (String askSizeDownload){ String selectQuery = "SELECT * FROM " + TABLE_ATTRIBUTES; ContentValues values = new ContentValues(); @@ -3998,12 +3955,6 @@ public void clearPreferences(){ onCreate(db); } -// public void clearOffline(){ -// log("clearOffline"); -// db.execSQL("DROP TABLE IF EXISTS " + TABLE_OFFLINE); -// onCreate(db); -// } - public void clearAttributes(){ long lastPublicHandle; long lastPublicHandleTimeStamp = -1; diff --git a/app/src/main/java/mega/privacy/android/app/DownloadService.java b/app/src/main/java/mega/privacy/android/app/DownloadService.java index 051f2fae0df..cd9c99850cc 100644 --- a/app/src/main/java/mega/privacy/android/app/DownloadService.java +++ b/app/src/main/java/mega/privacy/android/app/DownloadService.java @@ -25,29 +25,25 @@ import android.widget.Toast; import androidx.core.app.NotificationCompat; import androidx.core.content.ContextCompat; -import androidx.core.content.FileProvider; import android.widget.RemoteViews; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.schedulers.Schedulers; -import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.util.ArrayList; import java.util.HashMap; +import mega.privacy.android.app.components.saver.AutoPlayInfo; import mega.privacy.android.app.components.transferWidget.TransfersManagement; import mega.privacy.android.app.fragments.offline.OfflineFragment; import mega.privacy.android.app.lollipop.LoginActivityLollipop; import mega.privacy.android.app.lollipop.ManagerActivityLollipop; -import mega.privacy.android.app.lollipop.PdfViewerActivityLollipop; -import mega.privacy.android.app.lollipop.ZipBrowserActivityLollipop; import mega.privacy.android.app.lollipop.megachat.ChatSettings; import mega.privacy.android.app.notifications.TransferOverQuotaNotification; import mega.privacy.android.app.objects.SDTransfer; @@ -106,6 +102,7 @@ public class DownloadService extends Service implements MegaTransferListenerInte public static final String EXTRA_ZIP_FILE_TO_OPEN = "FILE_TO_OPEN"; public static final String EXTRA_OPEN_FILE = "OPEN_FILE"; public static final String EXTRA_CONTENT_URI = "CONTENT_URI"; + public static final String EXTRA_DOWNLOAD_BY_TAP = "EXTRA_DOWNLOAD_BY_TAP"; private static int errorEBloqued = 0; private int errorCount = 0; @@ -114,8 +111,8 @@ public class DownloadService extends Service implements MegaTransferListenerInte private boolean isForeground = false; private boolean canceled; - private String pathFileToOpen; private boolean openFile = true; + private boolean downloadByTap; private String type = ""; private boolean isOverquota = false; private long downloadedBytesToOverquota = 0; @@ -163,6 +160,11 @@ public class DownloadService extends Service implements MegaTransferListenerInte // the flag to determine the rating dialog is showed for this download action private boolean isRatingShowed; + /** + * Contains the info of a node that to be opened in-app. + */ + private AutoPlayInfo autoPlayInfo; + @SuppressLint("NewApi") @Override public void onCreate(){ @@ -335,6 +337,7 @@ protected void onHandleIntent(final Intent intent) { String url = intent.getStringExtra(EXTRA_URL); boolean isFolderLink = intent.getBooleanExtra(EXTRA_FOLDER_LINK, false); openFile = intent.getBooleanExtra(EXTRA_OPEN_FILE, true); + downloadByTap = intent.getBooleanExtra(EXTRA_DOWNLOAD_BY_TAP, false); type = intent.getStringExtra(EXTRA_TRANSFER_TYPE); Uri contentUri = null; @@ -414,13 +417,6 @@ protected void onHandleIntent(final Intent intent) { currentDocument = megaApi.getNodeByHandle(hash); } - if(intent.getStringExtra(EXTRA_ZIP_FILE_TO_OPEN)!=null){ - pathFileToOpen = intent.getStringExtra(EXTRA_ZIP_FILE_TO_OPEN); - } - else{ - pathFileToOpen=null; - } - if(url != null){ logDebug("Public node"); currentDir = new File(intent.getStringExtra(EXTRA_PATH)); @@ -566,7 +562,15 @@ private void onQueueComplete(long handle) { logDebug("onQueueComplete: total of files before reset " + total); if(total <= 0){ logDebug("onQueueComplete: reset total downloads"); - if (megaApi.getTotalDownloads() > 0) { + // When download a single file by tapping it, and auto play is enabled. + if (megaApi.getTotalDownloads() == 1 && Boolean.parseBoolean(dbH.getAutoPlayEnabled()) && autoPlayInfo != null && downloadByTap) { + sendBroadcast(new Intent(BROADCAST_ACTION_INTENT_SHOWSNACKBAR_TRANSFERS_FINISHED) + .putExtra(TRANSFER_TYPE, DOWNLOAD_TRANSFER_OPEN) + .putExtra(NODE_NAME, autoPlayInfo.getNodeName()) + .putExtra(NODE_HANDLE, autoPlayInfo.getNodeHandle()) + .putExtra(NUMBER_FILES, 1) + .putExtra(NODE_LOCAL_PATH, autoPlayInfo.getLocalPath())); + } else if (megaApi.getTotalDownloads() > 0) { sendBroadcast(new Intent(BROADCAST_ACTION_INTENT_SHOWSNACKBAR_TRANSFERS_FINISHED) .putExtra(TRANSFER_TYPE, DOWNLOAD_TRANSFER) .putExtra(NUMBER_FILES, megaApi.getTotalDownloads())); @@ -703,73 +707,27 @@ else if(errorCount>0){ try { boolean autoPlayEnabled = Boolean.parseBoolean(dbH.getAutoPlayEnabled()); if (openFile && autoPlayEnabled) { - logDebug("Both openFile and autoPlayEnabled are true"); - Boolean externalFile; - if (!currentFile.getAbsolutePath().contains(Environment.getExternalStorageDirectory().getPath())){ - externalFile = true; - } - else { - externalFile = false; - } + String fileLocalPath; + String path = getLocalFile(megaApi.getNodeByHandle(handle)); + if(path != null ) { + fileLocalPath = path; + } else { + fileLocalPath = currentFile.getAbsolutePath(); + } + + autoPlayInfo = new AutoPlayInfo(currentDocument.getName(), currentDocument.getHandle(), fileLocalPath, true); + logDebug("Both openFile and autoPlayEnabled are true"); boolean fromMV = false; if (fromMediaViewers.containsKey(handle)){ - fromMV = fromMediaViewers.get(handle); + Boolean result = fromMediaViewers.get(handle); + fromMV = result != null && result; } - if (MimeTypeList.typeForName(currentFile.getName()).isZip()){ - logDebug("Download success of zip file!"); - - if(pathFileToOpen!=null){ - Intent intentZip; - intentZip = new Intent(this, ZipBrowserActivityLollipop.class); - intentZip.setAction(ZipBrowserActivityLollipop.ACTION_OPEN_ZIP_FILE); - intentZip.putExtra(ZipBrowserActivityLollipop.EXTRA_ZIP_FILE_TO_OPEN, pathFileToOpen); - intentZip.putExtra(ZipBrowserActivityLollipop.EXTRA_PATH_ZIP, currentFile.getAbsolutePath()); - intentZip.putExtra(ZipBrowserActivityLollipop.EXTRA_HANDLE_ZIP, currentDocument.getHandle()); - - - if(intentZip!=null){ - intentZip.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intentZip); - } - } - else{ - Intent intentZip = null; - - intentZip = new Intent(this, ManagerActivityLollipop.class); - intentZip.setAction(ACTION_EXPLORE_ZIP); - intentZip.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intentZip.putExtra(EXTRA_PATH_ZIP, currentFile.getAbsolutePath()); - - startActivity(intentZip); - } - - logDebug("Launch intent to manager"); - } - else if (MimeTypeList.typeForName(currentFile.getName()).isPdf()){ + if (MimeTypeList.typeForName(currentFile.getName()).isPdf()){ logDebug("Pdf file"); - if (!fromMV) { - Intent pdfIntent = new Intent(this, PdfViewerActivityLollipop.class); - - pdfIntent.putExtra("HANDLE", handle); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !externalFile) { - pdfIntent.setDataAndType(FileProvider.getUriForFile(this, "mega.privacy.android.app.providers.fileprovider", currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } else { - pdfIntent.setDataAndType(Uri.fromFile(currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } - pdfIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - pdfIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - pdfIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } - pdfIntent.putExtra("fromDownloadService", true); - pdfIntent.putExtra("inside", true); - pdfIntent.putExtra("isUrl", false); - startActivity(pdfIntent); - } - else { + if(fromMV) { logDebug("Show notification"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_DOWNLOAD_ID, NOTIFICATION_CHANNEL_DOWNLOAD_NAME, NotificationManager.IMPORTANCE_DEFAULT); @@ -802,64 +760,7 @@ else if (MimeTypeList.typeForName(currentFile.getName()).isPdf()){ } else if (MimeTypeList.typeForName(currentFile.getName()).isVideoReproducible() || MimeTypeList.typeForName(currentFile.getName()).isAudio()) { logDebug("Video/Audio file"); - - if (!fromMV) { - Intent mediaIntent; - boolean internalIntent; - boolean opusFile = false; - if (MimeTypeList.typeForName(currentFile.getName()).isVideoNotSupported() || MimeTypeList.typeForName(currentFile.getName()).isAudioNotSupported()) { - mediaIntent = new Intent(Intent.ACTION_VIEW); - internalIntent = false; - String[] s = currentFile.getName().split("\\."); - if (s != null && s.length > 1 && s[s.length - 1].equals("opus")) { - opusFile = true; - } - } else { - internalIntent = true; - mediaIntent = getMediaIntent(this, currentFile.getName()); - } - - mediaIntent.putExtra(INTENT_EXTRA_KEY_IS_PLAYLIST, false); - mediaIntent.putExtra("HANDLE", handle); - mediaIntent.putExtra("fromDownloadService", true); - mediaIntent.putExtra(INTENT_EXTRA_KEY_FILE_NAME, currentFile.getName()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !externalFile) { - mediaIntent.setDataAndType(FileProvider.getUriForFile(this, "mega.privacy.android.app.providers.fileprovider", currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } else { - mediaIntent.setDataAndType(Uri.fromFile(currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } - if (opusFile) { - mediaIntent.setDataAndType(mediaIntent.getData(), "audio/*"); - } - mediaIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - mediaIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - mediaIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } - - if (internalIntent) { - startActivity(mediaIntent); - } - else { - if (isIntentAvailable(this, mediaIntent)) { - startActivity(mediaIntent); - } - else { - Intent intentShare = new Intent(Intent.ACTION_SEND); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && !externalFile) { - intentShare.setDataAndType(FileProvider.getUriForFile(this, "mega.privacy.android.app.providers.fileprovider", currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } else { - intentShare.setDataAndType(Uri.fromFile(currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } - intentShare.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intentShare.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (isIntentAvailable(this, mediaIntent)) { - startActivity(intentShare); - } - } - } - } - else { + if (fromMV) { logDebug("Show notification"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_DOWNLOAD_ID, NOTIFICATION_CHANNEL_DOWNLOAD_NAME, NotificationManager.IMPORTANCE_DEFAULT); @@ -889,68 +790,9 @@ else if (MimeTypeList.typeForName(currentFile.getName()).isVideoReproducible() | mNotificationManager.notify(NOTIFICATION_DOWNLOAD_FINAL, mBuilderCompat.build()); } } - } - else if (MimeTypeList.typeForName(currentFile.getName()).isDocument()) { - logDebug("Download is document"); - - Intent viewIntent = new Intent(Intent.ACTION_VIEW); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - viewIntent.setDataAndType(FileProvider.getUriForFile(this, "mega.privacy.android.app.providers.fileprovider", currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } else { - viewIntent.setDataAndType(Uri.fromFile(currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } - viewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - viewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } - - if (isIntentAvailable(this, viewIntent)) - startActivity(viewIntent); - else { - viewIntent.setAction(Intent.ACTION_GET_CONTENT); - - if (isIntentAvailable(this, viewIntent)) - startActivity(viewIntent); - else { - Intent intentShare = new Intent(Intent.ACTION_SEND); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - intentShare.setDataAndType(FileProvider.getUriForFile(this, "mega.privacy.android.app.providers.fileprovider", currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } else { - intentShare.setDataAndType(Uri.fromFile(currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } - intentShare.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intentShare.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - startActivity(intentShare); - } - } } else if (MimeTypeList.typeForName(currentFile.getName()).isImage()) { logDebug("Download is IMAGE"); - if (!fromMV){ - Intent viewIntent = new Intent(Intent.ACTION_VIEW); - // viewIntent.setDataAndType(Uri.fromFile(currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - viewIntent.setDataAndType(FileProvider.getUriForFile(this, "mega.privacy.android.app.providers.fileprovider", currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } else { - viewIntent.setDataAndType(Uri.fromFile(currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } - viewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - viewIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - - if (isIntentAvailable(this, viewIntent)) - startActivity(viewIntent); - else { - Intent intentShare = new Intent(Intent.ACTION_SEND); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - intentShare.setDataAndType(FileProvider.getUriForFile(this, "mega.privacy.android.app.providers.fileprovider", currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } else { - intentShare.setDataAndType(Uri.fromFile(currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } - intentShare.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intentShare.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - startActivity(intentShare); - } - } - else { + if (fromMV) { logDebug("Show notification"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_DOWNLOAD_ID, NOTIFICATION_CHANNEL_DOWNLOAD_NAME, NotificationManager.IMPORTANCE_DEFAULT); @@ -987,45 +829,7 @@ else if (MimeTypeList.typeForName(currentFile.getName()).isDocument()) { } } - } else if (MimeTypeList.typeForName(currentFile.getName()).isURL()) { - manageURLNode(this, megaApi, megaApi.getNodeByHandle(handle)); } else { - logDebug("Download is OTHER FILE"); - intent = new Intent(Intent.ACTION_VIEW); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - intent.setDataAndType(FileProvider.getUriForFile(this, "mega.privacy.android.app.providers.fileprovider", currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } else { - intent.setDataAndType(Uri.fromFile(currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } - - if (isIntentAvailable(this, intent)) - startActivity(intent); - else { - logWarning("Not intent available for ACTION_VIEW"); - intent.setAction(Intent.ACTION_GET_CONTENT); - - if (isIntentAvailable(this, intent)) - startActivity(intent); - else { - logWarning("Not intent available for ACTION_GET_CONTENT"); - intent.setAction(Intent.ACTION_SEND); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - intent.setDataAndType(FileProvider.getUriForFile(this, "mega.privacy.android.app.providers.fileprovider", currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } else { - intent.setDataAndType(Uri.fromFile(currentFile), MimeTypeList.typeForName(currentFile.getName()).getType()); - } - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } - startActivity(intent); - } - } - logDebug("Show notification"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_DOWNLOAD_ID, NOTIFICATION_CHANNEL_DOWNLOAD_NAME, NotificationManager.IMPORTANCE_DEFAULT); diff --git a/app/src/main/java/mega/privacy/android/app/MegaApplication.java b/app/src/main/java/mega/privacy/android/app/MegaApplication.java index 4c46ac1b080..636b0680c20 100644 --- a/app/src/main/java/mega/privacy/android/app/MegaApplication.java +++ b/app/src/main/java/mega/privacy/android/app/MegaApplication.java @@ -256,7 +256,7 @@ public static void smsVerifyShowed(boolean isShowed) { @Override public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { - + initLoggers(); } @Override @@ -527,10 +527,7 @@ public void run() { backgroundStatus = megaChatApi.getBackgroundStatus(); logDebug("backgroundStatus_activityVisible: " + backgroundStatus); if (backgroundStatus != -1 && backgroundStatus != 0) { - MegaHandleList callsInProgress = megaChatApi.getChatCalls(MegaChatCall.CALL_STATUS_IN_PROGRESS); - if (callsInProgress == null || callsInProgress.size() <= 0) { - megaChatApi.setBackgroundStatus(false); - } + megaChatApi.setBackgroundStatus(false); } } @@ -646,6 +643,10 @@ public void handleUncaughtException(Thread thread, Throwable e) { if (isRinging) { logDebug("Is incoming call"); incomingCall(listAllCalls, call.getChatid(), callStatus); + } else { + clearIncomingCallNotification(call.getCallId()); + getChatManagement().removeValues(call.getChatid()); + stopService(new Intent(getInstance(), IncomingCallService.class)); } }; @@ -739,8 +740,7 @@ public void onCreate() { keepAliveHandler.postAtTime(keepAliveRunnable, System.currentTimeMillis()+interval); keepAliveHandler.postDelayed(keepAliveRunnable, interval); - initLoggerSDK(); - initLoggerKarere(); + initLoggers(); checkAppUpgrade(); @@ -838,6 +838,27 @@ public void onFailed(@Nullable Throwable throwable) { ContextUtils.initialize(getApplicationContext()); Fresco.initialize(this); + + // Try to initialize the loggers again in order to avoid have them uninitialized + // in case they failed to initialize before for some reason. + initLoggers(); + } + + /** + * Initializes loggers if app storage is available and if are not initialized yet. + */ + private void initLoggers() { + if (getExternalFilesDir(null) == null) { + return; + } + + if (!isLoggerSDKInitialized()) { + initLoggerSDK(); + } + + if (!isLoggerKarereInitialized()) { + initLoggerKarere(); + } } public void askForFullAccountInfo(){ diff --git a/app/src/main/java/mega/privacy/android/app/MegaPreferences.java b/app/src/main/java/mega/privacy/android/app/MegaPreferences.java index c5fd07801a8..bb669725459 100644 --- a/app/src/main/java/mega/privacy/android/app/MegaPreferences.java +++ b/app/src/main/java/mega/privacy/android/app/MegaPreferences.java @@ -3,7 +3,7 @@ import static mega.privacy.android.app.utils.LogUtil.*; public class MegaPreferences{ - + String firstTime; String camSyncWifi; String camSyncEnabled; @@ -31,7 +31,6 @@ public class MegaPreferences{ String cameraFolderExternalSDCard; String passcodeLockType; String preferredSortCloud; - String preferredSortContacts; private String preferredSortCameraUpload; String preferredSortOthers; String firstTimeChat; @@ -65,15 +64,14 @@ public class MegaPreferences{ String keepFileNames, String storageAdvancedDevices, String preferredViewList, String preferredViewListCameraUploads, String uriExternalSDCard, String cameraFolderExternalSDCard, String passcodeLockType, - String preferredSortCloud, String preferredSortContacts, - String preferredSortOthers, String firstTimeChat, - String uploadVideoQuality, String conversionOnCharging, String chargingOnSize, - String shouldClearCameraSyncRecords, String camVideoSyncTimeStamp, - String secVideoSyncTimeStamp, String isAutoPlayEnabled, String removeGPS, - String showInviteBanner, String preferredSortCameraUpload, String sdCardUri, - String askForDisplayOver, String askForSetDownloadLocation, - String mediaSDCardUri, String isMediaOnSDCard, String passcodeLockRequireTime, - String fingerprintLock) { + String preferredSortCloud, String preferredSortOthers, String firstTimeChat, + String uploadVideoQuality, String conversionOnCharging, + String chargingOnSize, String shouldClearCameraSyncRecords, + String camVideoSyncTimeStamp, String secVideoSyncTimeStamp, + String isAutoPlayEnabled, String removeGPS, String showInviteBanner, + String preferredSortCameraUpload, String sdCardUri, String askForDisplayOver, + String askForSetDownloadLocation, String mediaSDCardUri, String isMediaOnSDCard, + String passcodeLockRequireTime, String fingerprintLock) { this.firstTime = firstTime; this.camSyncWifi = camSyncWifi; this.camSyncEnabled = camSyncEnabled; @@ -99,7 +97,6 @@ public class MegaPreferences{ this.cameraFolderExternalSDCard = cameraFolderExternalSDCard; this.passcodeLockType = passcodeLockType; this.preferredSortCloud = preferredSortCloud; - this.preferredSortContacts = preferredSortContacts; this.preferredSortOthers = preferredSortOthers; this.firstTimeChat = firstTimeChat; this.uploadVideoQuality = uploadVideoQuality; @@ -349,14 +346,6 @@ public void setPreferredSortCloud(String preferredSortCloud) { this.preferredSortCloud = preferredSortCloud; } - public String getPreferredSortContacts() { - return preferredSortContacts; - } - - public void setPreferredSortContacts(String preferredSortContacts) { - this.preferredSortContacts = preferredSortContacts; - } - public String getPreferredSortOthers() { return preferredSortOthers; } diff --git a/app/src/main/java/mega/privacy/android/app/OpenLinkActivity.java b/app/src/main/java/mega/privacy/android/app/OpenLinkActivity.java index 6bbcfec1860..cbc49764dff 100644 --- a/app/src/main/java/mega/privacy/android/app/OpenLinkActivity.java +++ b/app/src/main/java/mega/privacy/android/app/OpenLinkActivity.java @@ -8,8 +8,6 @@ import android.widget.RelativeLayout; import android.widget.TextView; -import org.jetbrains.annotations.NotNull; - import mega.privacy.android.app.activities.PasscodeActivity; import mega.privacy.android.app.activities.WebViewActivity; import mega.privacy.android.app.listeners.ConnectListener; @@ -24,17 +22,17 @@ import mega.privacy.android.app.meeting.activity.LeftMeetingActivity; import mega.privacy.android.app.meeting.fragments.MeetingHasEndedDialogFragment; import mega.privacy.android.app.utils.CallUtil; +import mega.privacy.android.app.utils.StringResourcesUtils; import mega.privacy.android.app.utils.TextUtil; import nz.mega.sdk.MegaApiAndroid; import nz.mega.sdk.MegaApiJava; import nz.mega.sdk.MegaChatApi; -import nz.mega.sdk.MegaChatApiJava; -import nz.mega.sdk.MegaChatError; import nz.mega.sdk.MegaChatRequest; import nz.mega.sdk.MegaError; -import nz.mega.sdk.MegaHandleList; import nz.mega.sdk.MegaRequest; import nz.mega.sdk.MegaRequestListenerInterface; + +import static mega.privacy.android.app.utils.CallUtil.showConfirmationInACall; import static mega.privacy.android.app.utils.Constants.*; import static mega.privacy.android.app.utils.LinksUtil.requiresTransferSession; import static mega.privacy.android.app.utils.LogUtil.logDebug; @@ -547,27 +545,33 @@ public void onPreviewLoaded(MegaChatRequest request, boolean alreadyExist) { if (type == LINK_IS_FOR_MEETING) { logDebug("It's a meeting link"); - if (CallUtil.isMeetingEnded(request.getMegaHandleList())) { - logDebug("Meeting has ended, open dialog"); - new MeetingHasEndedDialogFragment(new MeetingHasEndedDialogFragment.ClickCallback() { - @Override - public void onViewMeetingChat() { - goToChatActivity(); - } - - @Override - public void onLeave() { - goToGuestLeaveMeetingActivity(); - } - }).show(getSupportFragmentManager(), - MeetingHasEndedDialogFragment.TAG); - } else if (isFromOpenChatPreview) { - logDebug("Meeting is in progress, open join meeting"); - goToMeetingActivity(chatId, request.getText()); + if (CallUtil.participatingInACall()) { + showConfirmationInACall(this, StringResourcesUtils.getString(R.string.text_join_call), passcodeManagement); } else { - logDebug("It's a meeting, open chat preview"); - megaChatApi.openChatPreview(url, new LoadPreviewListener(this, OpenLinkActivity.this, CHECK_LINK_TYPE_MEETING_LINK)); + if (CallUtil.isMeetingEnded(request.getMegaHandleList())) { + logDebug("Meeting has ended, open dialog"); + new MeetingHasEndedDialogFragment(new MeetingHasEndedDialogFragment.ClickCallback() { + @Override + public void onViewMeetingChat() { + goToChatActivity(); + } + + @Override + public void onLeave() { + goToGuestLeaveMeetingActivity(); + } + }).show(getSupportFragmentManager(), + MeetingHasEndedDialogFragment.TAG); + } else if (isFromOpenChatPreview) { + logDebug("Meeting is in progress, open join meeting"); + goToMeetingActivity(chatId, request.getText()); + } else { + logDebug("It's a meeting, open chat preview"); + logDebug("openChatPreview"); + megaChatApi.openChatPreview(url, new LoadPreviewListener(this, OpenLinkActivity.this, CHECK_LINK_TYPE_MEETING_LINK)); + } } + } else { logDebug("It's a chat link"); goToChatActivity(); diff --git a/app/src/main/java/mega/privacy/android/app/activities/settingsActivities/FileManagementPreferencesActivity.java b/app/src/main/java/mega/privacy/android/app/activities/settingsActivities/FileManagementPreferencesActivity.java index bc6baa534ff..a5a27613698 100644 --- a/app/src/main/java/mega/privacy/android/app/activities/settingsActivities/FileManagementPreferencesActivity.java +++ b/app/src/main/java/mega/privacy/android/app/activities/settingsActivities/FileManagementPreferencesActivity.java @@ -173,8 +173,13 @@ protected void onCreate(Bundle savedInstanceState) { setTitle(R.string.settings_file_management_category); - sttFileManagement = new SettingsFileManagementFragment(); - replaceFragment(sttFileManagement); + if (savedInstanceState == null) { + sttFileManagement = new SettingsFileManagementFragment(); + replaceFragment(sttFileManagement); + } else { + sttFileManagement = (SettingsFileManagementFragment) + getSupportFragmentManager().findFragmentById(R.id.fragment_container); + } registerReceiver(cacheSizeUpdateReceiver, new IntentFilter(ACTION_UPDATE_CACHE_SIZE_SETTING)); diff --git a/app/src/main/java/mega/privacy/android/app/activities/settingsActivities/PasscodeLockActivity.kt b/app/src/main/java/mega/privacy/android/app/activities/settingsActivities/PasscodeLockActivity.kt index 17b63068e1e..48262010c24 100644 --- a/app/src/main/java/mega/privacy/android/app/activities/settingsActivities/PasscodeLockActivity.kt +++ b/app/src/main/java/mega/privacy/android/app/activities/settingsActivities/PasscodeLockActivity.kt @@ -149,6 +149,11 @@ class PasscodeLockActivity : BaseActivity() { setListeners() } + override fun onResume() { + super.onResume() + passcodeUtil.resetLastPauseUpdate() + } + /** * Increments the number of failed attempts. */ diff --git a/app/src/main/java/mega/privacy/android/app/components/GroupParticipantsDividerItemDecoration.java b/app/src/main/java/mega/privacy/android/app/components/GroupParticipantsDividerItemDecoration.java deleted file mode 100644 index 50e2cabdae6..00000000000 --- a/app/src/main/java/mega/privacy/android/app/components/GroupParticipantsDividerItemDecoration.java +++ /dev/null @@ -1,23 +0,0 @@ -package mega.privacy.android.app.components; - -import android.content.Context; -import android.graphics.Canvas; -import android.view.View; - -import androidx.recyclerview.widget.RecyclerView; - -import static mega.privacy.android.app.lollipop.megachat.chatAdapters.MegaParticipantsChatLollipopAdapter.ITEM_VIEW_TYPE_HEADER; - -public class GroupParticipantsDividerItemDecoration extends HeaderItemDecoration { - - public GroupParticipantsDividerItemDecoration(Context context) { - super(context); - } - - @Override - protected void drawDivider(Canvas c, RecyclerView parent, View child, int viewType) { - if (viewType != ITEM_VIEW_TYPE_HEADER) { - drawDivider(c, parent, child); - } - } -} diff --git a/app/src/main/java/mega/privacy/android/app/components/PositionDividerItemDecoration.java b/app/src/main/java/mega/privacy/android/app/components/PositionDividerItemDecoration.java index 3374db19390..24229ba58e8 100644 --- a/app/src/main/java/mega/privacy/android/app/components/PositionDividerItemDecoration.java +++ b/app/src/main/java/mega/privacy/android/app/components/PositionDividerItemDecoration.java @@ -1,5 +1,7 @@ package mega.privacy.android.app.components; +import static mega.privacy.android.app.utils.Constants.INVALID_POSITION; + import android.content.Context; import android.graphics.Canvas; import android.graphics.drawable.Drawable; @@ -64,10 +66,7 @@ private boolean draw(int pos) { return true; } - if (pos == 0 || (position != -1 && position-1 == pos) - || (position != -1 && position == pos)) { - return false; - } - return true; + return pos != 0 && (position == INVALID_POSITION || position - 1 != pos) + && (position == INVALID_POSITION || position != pos); } } diff --git a/app/src/main/java/mega/privacy/android/app/components/saver/MegaNodeSaving.kt b/app/src/main/java/mega/privacy/android/app/components/saver/MegaNodeSaving.kt index 924c37d873d..b4dec3ad2f0 100644 --- a/app/src/main/java/mega/privacy/android/app/components/saver/MegaNodeSaving.kt +++ b/app/src/main/java/mega/privacy/android/app/components/saver/MegaNodeSaving.kt @@ -29,7 +29,8 @@ class MegaNodeSaving( private val fromMediaViewer: Boolean, private val needSerialize: Boolean, private val isVoiceClip: Boolean = false, - private val downloadToGallery: Boolean = false + private val downloadToGallery: Boolean = false, + private val downloadByTap: Boolean = false ) : Saving() { override fun totalSize() = totalSize @@ -160,6 +161,8 @@ class MegaNodeSaving( intent.putExtra(HIGH_PRIORITY_TRANSFER, true) } + intent.putExtra(EXTRA_DOWNLOAD_BY_TAP, downloadByTap) + if (fromMediaViewer) { intent.putExtra(EXTRA_FROM_MV, true) } diff --git a/app/src/main/java/mega/privacy/android/app/components/saver/NodeSaver.kt b/app/src/main/java/mega/privacy/android/app/components/saver/NodeSaver.kt index 151429bed56..227358a8b29 100644 --- a/app/src/main/java/mega/privacy/android/app/components/saver/NodeSaver.kt +++ b/app/src/main/java/mega/privacy/android/app/components/saver/NodeSaver.kt @@ -3,12 +3,10 @@ package mega.privacy.android.app.components.saver import android.Manifest.permission import android.app.Activity import android.content.Intent -import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle import android.os.StatFs import android.text.TextUtils -import androidx.core.content.ContextCompat import androidx.documentfile.provider.DocumentFile import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Completable @@ -240,6 +238,8 @@ class NodeSaver( * @param isFolderLink whether this download is a folder link * @param fromMediaViewer whether this download is from media viewer * @param needSerialize whether this download need serialize + * @param downloadToGallery whether this download is download to gallery + * @param downloadByTap whether this download is triggered by tap */ @JvmOverloads fun saveNodes( @@ -248,12 +248,13 @@ class NodeSaver( isFolderLink: Boolean = false, fromMediaViewer: Boolean = false, needSerialize: Boolean = false, - downloadToGallery: Boolean = false + downloadToGallery: Boolean = false, + downloadByTap: Boolean = false ) { save { MegaNodeSaving( nodesTotalSize(nodes), highPriority, isFolderLink, nodes, fromMediaViewer, - needSerialize, downloadToGallery = downloadToGallery + needSerialize, downloadToGallery = downloadToGallery, downloadByTap = downloadByTap ) } } diff --git a/app/src/main/java/mega/privacy/android/app/constants/BroadcastConstants.java b/app/src/main/java/mega/privacy/android/app/constants/BroadcastConstants.java index df217282736..6647c0dab9a 100644 --- a/app/src/main/java/mega/privacy/android/app/constants/BroadcastConstants.java +++ b/app/src/main/java/mega/privacy/android/app/constants/BroadcastConstants.java @@ -75,6 +75,7 @@ public class BroadcastConstants { public static final String EXTRA_USER_HANDLE = "USER_HANDLE"; public static final String TRANSFER_TYPE = "TRANSFER_TYPE"; public static final String DOWNLOAD_TRANSFER = "DOWNLOAD_TRANSFER"; + public static final String DOWNLOAD_TRANSFER_OPEN = "DOWNLOAD_TRANSFER_OPEN"; public static final String UPLOAD_TRANSFER = "UPLOAD_TRANSFER"; public static final String EXTRA_IS_CU_SECONDARY_FOLDER = "EXTRA_IS_CU_SECONDARY_FOLDER"; public static final String UPDATE_PROXIMITY_SENSOR_STATUS= "UPDATE_PROXIMITY_SENSOR_STATUS"; @@ -95,4 +96,7 @@ public class BroadcastConstants { public static final String ERROR_MESSAGE_TEXT = "ERROR_MESSAGE_TEXT"; public static final String PROGRESS = "PROGRESS"; public static final String PENDING_TRANSFERS = "PENDING_TRANSFERS"; + public static final String NODE_NAME = "NODE_NAME"; + public static final String NODE_HANDLE = "NODE_HANDLE"; + public static final String NODE_LOCAL_PATH = "NODE_LOCAL_PATH"; } diff --git a/app/src/main/java/mega/privacy/android/app/constants/EventConstants.kt b/app/src/main/java/mega/privacy/android/app/constants/EventConstants.kt index 34e5827ff09..bee30bac90c 100644 --- a/app/src/main/java/mega/privacy/android/app/constants/EventConstants.kt +++ b/app/src/main/java/mega/privacy/android/app/constants/EventConstants.kt @@ -55,4 +55,7 @@ object EventConstants { const val EVENT_UPDATE_HIDE_RECENT_ACTIVITY = "EVENT_UPDATE_HIDE_RECENT_ACTIVITY" const val EVENT_UPDATE_START_SCREEN = "EVENT_UPDATE_START_SCREEN" + + const val EVENT_UPDATE_VIEW_MODE = "EVENT_UPDATE_VIEW_MODE" + } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/constants/SettingsConstants.java b/app/src/main/java/mega/privacy/android/app/constants/SettingsConstants.java index 7d8d5f51afa..6ff1410bd52 100644 --- a/app/src/main/java/mega/privacy/android/app/constants/SettingsConstants.java +++ b/app/src/main/java/mega/privacy/android/app/constants/SettingsConstants.java @@ -114,9 +114,6 @@ public class SettingsConstants { /* Cookie settings */ public static final String KEY_COOKIE_ACCEPT = "settings_cookie_accept"; public static final String KEY_COOKIE_ESSENTIAL = "settings_cookie_essential"; - public static final String KEY_COOKIE_PREFERENCE = "settings_cookie_preference"; public static final String KEY_COOKIE_ANALYTICS = "settings_cookie_performance_analytics"; - public static final String KEY_COOKIE_ADVERTISING = "settings_cookie_advertising"; - public static final String KEY_COOKIE_THIRD_PARTY = "settings_cookie_third_party"; public static final String KEY_COOKIE_POLICIES = "setting_cookie_policies"; } diff --git a/app/src/main/java/mega/privacy/android/app/contacts/group/ContactGroupsFragment.kt b/app/src/main/java/mega/privacy/android/app/contacts/group/ContactGroupsFragment.kt index f71ec6cf13f..df15c260e8b 100644 --- a/app/src/main/java/mega/privacy/android/app/contacts/group/ContactGroupsFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/contacts/group/ContactGroupsFragment.kt @@ -1,5 +1,6 @@ package mega.privacy.android.app.contacts.group +import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.LayoutInflater @@ -7,6 +8,8 @@ import android.view.Menu import android.view.MenuInflater import android.view.View import android.view.ViewGroup +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.core.content.res.ResourcesCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -19,13 +22,19 @@ import mega.privacy.android.app.contacts.ContactsActivity import mega.privacy.android.app.contacts.group.adapter.ContactGroupsAdapter import mega.privacy.android.app.contacts.group.data.ContactGroupItem import mega.privacy.android.app.databinding.FragmentContactGroupsBinding +import mega.privacy.android.app.interfaces.SnackbarShower +import mega.privacy.android.app.interfaces.showSnackbar import mega.privacy.android.app.lollipop.AddContactActivityLollipop +import mega.privacy.android.app.lollipop.megachat.ChatActivityLollipop import mega.privacy.android.app.lollipop.megachat.GroupChatInfoActivityLollipop import mega.privacy.android.app.utils.Constants import mega.privacy.android.app.utils.Constants.MIN_ITEMS_SCROLLBAR +import mega.privacy.android.app.utils.LogUtil import mega.privacy.android.app.utils.MenuUtils.setupSearchView +import mega.privacy.android.app.utils.StringResourcesUtils import mega.privacy.android.app.utils.StringUtils.formatColorTag import mega.privacy.android.app.utils.StringUtils.toSpannedHtmlText +import nz.mega.sdk.MegaChatApiJava.MEGACHAT_INVALID_HANDLE /** * Fragment that represents the UI showing the list of contact groups for the current user. @@ -37,6 +46,52 @@ class ContactGroupsFragment : Fragment() { private val viewModel by viewModels() private val groupsAdapter by lazy { ContactGroupsAdapter(::onGroupClick) } + private lateinit var createGroupChatLauncher: ActivityResultLauncher + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + createGroupChatLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + val intent = result.data + val resultCode = result.resultCode + if (resultCode != Activity.RESULT_OK || intent == null) { + LogUtil.logWarning("Error creating chat") + return@registerForActivityResult + } + + val contactsData = + intent.getStringArrayListExtra(AddContactActivityLollipop.EXTRA_CONTACTS) + val isGroup = + intent.getBooleanExtra(AddContactActivityLollipop.EXTRA_GROUP_CHAT, false) + + if (contactsData == null || !isGroup) { + LogUtil.logWarning("Is one to one chat or no contacts selected") + return@registerForActivityResult + } + + val chatTitle = + intent.getStringExtra(AddContactActivityLollipop.EXTRA_CHAT_TITLE) + + viewModel.getGroupChatRoom(contactsData, chatTitle) + .observe(viewLifecycleOwner) { chatId -> + if (chatId == MEGACHAT_INVALID_HANDLE) { + (requireActivity() as SnackbarShower).showSnackbar( + StringResourcesUtils.getString(R.string.create_chat_error) + ) + } else { + Intent( + requireContext(), + ChatActivityLollipop::class.java + ).apply { + action = Constants.ACTION_CHAT_SHOW_MESSAGES + putExtra(Constants.CHAT_ID, chatId) + startActivity(this) + } + } + } + } + } override fun onCreateView( inflater: LayoutInflater, @@ -84,10 +139,14 @@ class ContactGroupsFragment : Fragment() { binding.listScroller.setRecyclerView(binding.list) binding.btnCreateGroup.setOnClickListener { - startActivity(Intent(requireContext(), AddContactActivityLollipop::class.java).apply { + val intent = Intent( + requireContext(), + AddContactActivityLollipop::class.java + ).apply { putExtra(AddContactActivityLollipop.EXTRA_CONTACT_TYPE, Constants.CONTACT_TYPE_MEGA) putExtra(AddContactActivityLollipop.EXTRA_ONLY_CREATE_GROUP, true) - }) + } + intentToCreateGroupChat(intent) } binding.viewEmpty.text = binding.viewEmpty.text.toString() @@ -96,6 +155,13 @@ class ContactGroupsFragment : Fragment() { .toSpannedHtmlText() } + /** + * Launches an Intent to open create group chat room screen. + */ + private fun intentToCreateGroupChat(intent: Intent) { + createGroupChatLauncher.launch(intent) + } + private fun setupObservers() { viewModel.getGroups().observe(viewLifecycleOwner, ::showGroups) } diff --git a/app/src/main/java/mega/privacy/android/app/contacts/group/ContactGroupsViewModel.kt b/app/src/main/java/mega/privacy/android/app/contacts/group/ContactGroupsViewModel.kt index c09e5ef4fbd..757d6a97283 100644 --- a/app/src/main/java/mega/privacy/android/app/contacts/group/ContactGroupsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/contacts/group/ContactGroupsViewModel.kt @@ -10,9 +10,12 @@ import io.reactivex.rxjava3.kotlin.subscribeBy import io.reactivex.rxjava3.schedulers.Schedulers import mega.privacy.android.app.arch.BaseRxViewModel import mega.privacy.android.app.contacts.group.data.ContactGroupItem +import mega.privacy.android.app.contacts.usecase.CreateGroupChatUseCase import mega.privacy.android.app.contacts.usecase.GetContactGroupsUseCase import mega.privacy.android.app.utils.LogUtil.logError import mega.privacy.android.app.utils.notifyObserver +import nz.mega.sdk.MegaChatApiJava.MEGACHAT_INVALID_HANDLE +import java.util.ArrayList /** * ViewModel that handles all related logic to Contact Groups for the current user. @@ -20,7 +23,8 @@ import mega.privacy.android.app.utils.notifyObserver * @param getContactGroupsUseCase UseCase to retrieve all contact groups. */ class ContactGroupsViewModel @ViewModelInject constructor( - getContactGroupsUseCase: GetContactGroupsUseCase + getContactGroupsUseCase: GetContactGroupsUseCase, + private val getGroupChatRoomUseCase: CreateGroupChatUseCase ) : BaseRxViewModel() { private val groups: MutableLiveData> = MutableLiveData() @@ -54,4 +58,33 @@ class ContactGroupsViewModel @ViewModelInject constructor( queryString = query groups.notifyObserver() } + + /** + * Method for creating a group chat room when multiple contacts have been selected + * + * @param contactsData List of contacts who will participate in the chat + * @param chatTitle Title of the group chat room + * @return chat ID of the new group chat room + */ + fun getGroupChatRoom( + contactsData: ArrayList, + chatTitle: String? + ): LiveData { + val result = MutableLiveData() + getGroupChatRoomUseCase.getGroupChatRoomCreated(contactsData, chatTitle) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeBy( + onSuccess = { chatId -> + result.value = chatId + }, + onError = { error -> + logError(error.stackTraceToString()) + result.value = MEGACHAT_INVALID_HANDLE + } + ) + .addTo(composite) + + return result + } } diff --git a/app/src/main/java/mega/privacy/android/app/contacts/list/dialog/ContactBottomSheetDialogFragment.kt b/app/src/main/java/mega/privacy/android/app/contacts/list/dialog/ContactBottomSheetDialogFragment.kt index 38b4b16c0c3..5d52c79e47f 100644 --- a/app/src/main/java/mega/privacy/android/app/contacts/list/dialog/ContactBottomSheetDialogFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/contacts/list/dialog/ContactBottomSheetDialogFragment.kt @@ -190,8 +190,15 @@ class ContactBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { } binding.optionCall.setOnClickListener { - CallUtil.startNewCall(activity, activity as SnackbarShower, megaUser, passcodeManagement) - dismiss() + if (CallUtil.canCallBeStartedFromContactOption(requireActivity(), passcodeManagement)) { + CallUtil.startNewCall( + activity, + activity as SnackbarShower, + megaUser, + passcodeManagement + ) + dismiss() + } } binding.optionSendMessage.setOnClickListener { diff --git a/app/src/main/java/mega/privacy/android/app/contacts/usecase/CreateGroupChatUseCase.kt b/app/src/main/java/mega/privacy/android/app/contacts/usecase/CreateGroupChatUseCase.kt new file mode 100644 index 00000000000..4f2fbfc2d7b --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/contacts/usecase/CreateGroupChatUseCase.kt @@ -0,0 +1,63 @@ +package mega.privacy.android.app.contacts.usecase + +import io.reactivex.rxjava3.core.Single +import mega.privacy.android.app.di.MegaApi +import mega.privacy.android.app.listeners.OptionalMegaChatRequestListenerInterface +import mega.privacy.android.app.utils.ErrorUtils.toThrowable +import mega.privacy.android.app.utils.LogUtil.logError +import nz.mega.sdk.* +import nz.mega.sdk.MegaContactRequest.* +import nz.mega.sdk.MegaError.API_OK +import javax.inject.Inject + +/** + * Use case to create group chat from contacts selected + * + * @property megaApi MegaApi required to call the SDK + * @property megaChatApi MegaChatApi required to call the MegaChatSDK + */ +class CreateGroupChatUseCase @Inject constructor( + @MegaApi private val megaApi: MegaApiAndroid, + private val megaChatApi: MegaChatApiAndroid +) { + + /** + * Create and get a group chat room + * + * @param contactsData List of selected contacts + * @param chatTitle Title of new chat room + * @return Chat ID of the new chat room + */ + fun getGroupChatRoomCreated(contactsData: ArrayList, chatTitle: String?): Single = + Single.create { emitter -> + val peerList = MegaChatPeerList.createInstance() + contactsData.forEach { email -> + val contact = megaApi.getContact(email) + if (contact != null) { + peerList.addPeer(contact.handle, MegaChatPeerList.PRIV_STANDARD) + } + } + + if (peerList.size() == 0) { + emitter.onError(IllegalArgumentException("No contacts")) + return@create + } + + megaChatApi.createPublicChat(peerList, + chatTitle, + OptionalMegaChatRequestListenerInterface( + onRequestFinish = { request: MegaChatRequest, error: MegaChatError -> + if (emitter.isDisposed) return@OptionalMegaChatRequestListenerInterface + + if (error.errorCode == API_OK) { + emitter.onSuccess(request.chatHandle) + } else { + emitter.onError(error.toThrowable()) + } + }, + onRequestTemporaryError = { _: MegaChatRequest, error: MegaChatError -> + logError(error.toThrowable().stackTraceToString()) + } + )) + } +} \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/fragments/MegaNodeBaseFragment.java b/app/src/main/java/mega/privacy/android/app/fragments/MegaNodeBaseFragment.java index 6b1efa33ec9..a3a527aae39 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/MegaNodeBaseFragment.java +++ b/app/src/main/java/mega/privacy/android/app/fragments/MegaNodeBaseFragment.java @@ -11,6 +11,7 @@ import androidx.core.content.FileProvider; import androidx.appcompat.view.ActionMode; import androidx.core.text.HtmlCompat; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -31,28 +32,24 @@ import org.jetbrains.annotations.NotNull; -import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Stack; import javax.inject.Inject; import dagger.hilt.android.AndroidEntryPoint; +import kotlin.Unit; import mega.privacy.android.app.MegaPreferences; 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.SimpleDividerItemDecoration; +import mega.privacy.android.app.components.PositionDividerItemDecoration; import mega.privacy.android.app.components.scrollBar.FastScroller; +import mega.privacy.android.app.fragments.homepage.EventObserver; +import mega.privacy.android.app.fragments.homepage.SortByHeaderViewModel; import mega.privacy.android.app.fragments.managerFragments.LinksFragment; import mega.privacy.android.app.globalmanagement.SortOrderManagement; import mega.privacy.android.app.interfaces.SnackbarShower; @@ -81,6 +78,7 @@ import static mega.privacy.android.app.utils.MegaApiUtils.*; import static mega.privacy.android.app.utils.MegaNodeUtil.manageTextFileIntent; import static mega.privacy.android.app.utils.MegaNodeUtil.manageURLNode; +import static mega.privacy.android.app.utils.MegaNodeUtil.onNodeTapped; import static mega.privacy.android.app.utils.MegaNodeUtil.showConfirmationLeaveIncomingShares; import static mega.privacy.android.app.utils.Util.*; import static nz.mega.sdk.MegaApiJava.*; @@ -113,6 +111,8 @@ public abstract class MegaNodeBaseFragment extends RotatableFragment { protected LinearLayout emptyLinearLayout; protected TextView emptyTextViewFirst; + protected SortByHeaderViewModel sortByHeaderViewModel; + protected abstract void setNodes(ArrayList nodes); protected abstract void setEmptyView(); @@ -128,6 +128,17 @@ public MegaNodeBaseFragment() { downloadLocationDefaultPath = getDownloadLocation(); } + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + sortByHeaderViewModel = new ViewModelProvider(this).get(SortByHeaderViewModel.class); + + sortByHeaderViewModel.getShowDialogEvent().observe(getViewLifecycleOwner(), + new EventObserver<>(this::showSortByPanel)); + + return super.onCreateView(inflater, container, savedInstanceState); + } + protected abstract class BaseActionBarCallBack implements ActionMode.Callback { protected List selected; @@ -353,6 +364,20 @@ protected void updateActionModeTitle() { } } + /** + * Shows the Sort by panel. + * + * @param unit Unit event. + * @return Null. + */ + protected Unit showSortByPanel(Unit unit) { + managerActivity.showNewSortByPanel(getCurrentSharesTab() == INCOMING_TAB + ? ORDER_OTHERS + : ORDER_CLOUD); + + return null; + } + public ActionMode getActionMode() { return actionMode; } @@ -435,11 +460,10 @@ protected void checkEmptyView() { public void openFile(MegaNode node, int fragmentAdapter, int position) { MimeTypeList mimeType = MimeTypeList.typeForName(node.getName()); String mimeTypeType = mimeType.getType(); - Intent intent = null; + Intent intent; boolean internalIntent = false; if (mimeType.isImage()) { - internalIntent = true; intent = new Intent(context, FullScreenImageViewerLollipop.class); intent.putExtra("placeholder", adapter.getPlaceholderCount()); intent.putExtra("position", position); @@ -448,12 +472,13 @@ public void openFile(MegaNode node, int fragmentAdapter, int position) { intent.putExtra("parentNodeHandle", getParentHandle(fragmentAdapter)); intent.putExtra("orderGetChildren", getIntentOrder(fragmentAdapter)); intent.putExtra(INTENT_EXTRA_KEY_HANDLE, node.getHandle()); + + launchIntent(intent, true, position); } else if (mimeType.isVideoReproducible() || mimeType.isAudio()) { boolean opusFile = false; if (mimeType.isVideoNotSupported() || mimeType.isAudioNotSupported()) { intent = new Intent(Intent.ACTION_VIEW); - internalIntent = false; String[] s = node.getName().split("\\."); opusFile = s.length > 1 && s[s.length - 1].equals("opus"); } else { @@ -516,9 +541,10 @@ public void openFile(MegaNode node, int fragmentAdapter, int position) { if (opusFile) { intent.setDataAndType(intent.getData(), "audio/*"); } + + launchIntent(intent, internalIntent, position); } else if (mimeType.isURL()) { manageURLNode(context, megaApi, node); - return; } else if (mimeType.isPdf()) { logDebug("isFile:isPdf"); intent = new Intent(context, PdfViewerActivityLollipop.class); @@ -567,24 +593,33 @@ public void openFile(MegaNode node, int fragmentAdapter, int position) { return; } } + + launchIntent(intent, false, position); } else if (mimeType.isOpenableTextFile(node.getSize())) { manageTextFileIntent(requireContext(), node, fragmentAdapter); - return; + } else { + logDebug("itemClick:isFile:otherOption"); + onNodeTapped(context, node, managerActivity::saveNodeByTap, managerActivity, managerActivity); } + } + /** + * Launch corresponding intent to open the file based on its type. + * + * @param intent Intent to launch activity. + * @param internalIntent true, if the intent is for launching an intent in-app; false, otherwise. + * @param position Clicked item position. + */ + private void launchIntent(Intent intent, boolean internalIntent, int position) { if (intent != null) { if (internalIntent || isIntentAvailable(context, intent)) { putThumbnailLocation(intent, recyclerView, position, viewerFrom(), adapter); context.startActivity(intent); managerActivity.overridePendingTransition(0, 0); - - return; } else { Toast.makeText(context, context.getResources().getString(R.string.intent_not_available), Toast.LENGTH_LONG).show(); } } - - managerActivity.saveNodesToDevice(Collections.singletonList(node), true, false, false, false); } protected abstract int viewerFrom(); @@ -612,7 +647,7 @@ protected View getListView(LayoutInflater inflater, ViewGroup container) { recyclerView = v.findViewById(R.id.file_list_view_browser); mLayoutManager = new LinearLayoutManager(context); recyclerView.setLayoutManager(mLayoutManager); - recyclerView.addItemDecoration(new SimpleDividerItemDecoration(requireContext())); + recyclerView.addItemDecoration(new PositionDividerItemDecoration(requireContext(), getOutMetrics())); fastScroller = v.findViewById(R.id.fastscroll); setRecyclerView(); recyclerView.setItemAnimator(noChangeRecyclerViewItemAnimator()); @@ -648,6 +683,25 @@ protected View getGridView(LayoutInflater inflater, ViewGroup container) { return v; } + /** + * Gets the current shares tab depending on the current Fragment instance. + * + * @return The current shares tab. + */ + private int getCurrentSharesTab() { + int tab = ERROR_TAB; + + if (MegaNodeBaseFragment.this instanceof IncomingSharesFragmentLollipop) { + tab = INCOMING_TAB; + } else if (MegaNodeBaseFragment.this instanceof OutgoingSharesFragmentLollipop) { + tab = OUTGOING_TAB; + } else if (MegaNodeBaseFragment.this instanceof LinksFragment) { + tab = LINKS_TAB; + } + + return tab; + } + private void setRecyclerView() { recyclerView.setPadding(0, 0, 0, dp2px(MARGIN_BOTTOM_LIST, outMetrics)); recyclerView.setHasFixedSize(true); @@ -656,14 +710,7 @@ private void setRecyclerView() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); - int tab = ERROR_TAB; - if (MegaNodeBaseFragment.this instanceof IncomingSharesFragmentLollipop) { - tab = INCOMING_TAB; - } else if (MegaNodeBaseFragment.this instanceof OutgoingSharesFragmentLollipop) { - tab = OUTGOING_TAB; - } else if (MegaNodeBaseFragment.this instanceof LinksFragment) { - tab = LINKS_TAB; - } + int tab = getCurrentSharesTab(); if (managerActivity.getTabItemShares() == tab) { checkScroll(); diff --git a/app/src/main/java/mega/privacy/android/app/fragments/homepage/NodeViewHolder.kt b/app/src/main/java/mega/privacy/android/app/fragments/homepage/NodeViewHolder.kt index a9d00f909bb..b4387b116ee 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/homepage/NodeViewHolder.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/homepage/NodeViewHolder.kt @@ -43,7 +43,7 @@ class NodeViewHolder(private val binding: ViewDataBinding) : } is SortByHeaderBinding -> { this.orderNameStringId = - SortByHeaderViewModel.orderNameMap[sortByHeaderViewModel.order]!! + SortByHeaderViewModel.orderNameMap[sortByHeaderViewModel.order.first]!! this.sortByHeaderViewModel = sortByHeaderViewModel } } 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 f3be908d52c..1ea14567eae 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 @@ -1,25 +1,35 @@ package mega.privacy.android.app.fragments.homepage -import android.content.Context -import android.content.Intent import androidx.hilt.lifecycle.ViewModelInject import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import androidx.lifecycle.ViewModel import com.jeremyliao.liveeventbus.LiveEventBus -import dagger.hilt.android.qualifiers.ApplicationContext import mega.privacy.android.app.R -import mega.privacy.android.app.utils.Constants +import mega.privacy.android.app.constants.EventConstants.EVENT_UPDATE_VIEW_MODE +import mega.privacy.android.app.globalmanagement.SortOrderManagement import mega.privacy.android.app.utils.Constants.EVENT_LIST_GRID_CHANGE import mega.privacy.android.app.utils.Constants.EVENT_ORDER_CHANGE import nz.mega.sdk.MegaApiJava.* +/** + * ViewModel in charge of manage actions from sub-headers in which view mode (list or grid) + * and sort by options can be changed. + */ class SortByHeaderViewModel @ViewModelInject constructor( - @ApplicationContext private val context: Context + sortOrderManagement: SortOrderManagement ) : ViewModel() { - var order = ORDER_DEFAULT_ASC + /* Triple: + - First: Cloud order + - Second: Others order (Incoming root) + - Third: Offline order */ + var order = Triple( + sortOrderManagement.getOrderCloud(), + sortOrderManagement.getOrderOthers(), + sortOrderManagement.getOrderOffline() + ) private set var isList = true private set @@ -27,13 +37,13 @@ class SortByHeaderViewModel @ViewModelInject constructor( private val _showDialogEvent = MutableLiveData>() val showDialogEvent: LiveData> = _showDialogEvent - private val _orderChangeEvent = MutableLiveData>() - val orderChangeEvent: LiveData> = _orderChangeEvent + private val _orderChangeEvent = MutableLiveData>>() + val orderChangeEvent: LiveData>> = _orderChangeEvent private val _listGridChangeEvent = MutableLiveData>() val listGridChangeEvent: LiveData> = _listGridChangeEvent - private val orderChangeObserver = Observer { + private val orderChangeObserver = Observer> { order = it _orderChangeEvent.value = Event(it) } @@ -45,8 +55,9 @@ class SortByHeaderViewModel @ViewModelInject constructor( init { // Use "sticky" to observe the value set in ManagerActivity onCreate() - LiveEventBus.get(EVENT_ORDER_CHANGE, Int::class.java) - .observeStickyForever(orderChangeObserver) + @Suppress("UNCHECKED_CAST") + LiveEventBus.get(EVENT_ORDER_CHANGE) + .observeStickyForever(orderChangeObserver as Observer) LiveEventBus.get( EVENT_LIST_GRID_CHANGE, Boolean::class.java @@ -58,20 +69,21 @@ class SortByHeaderViewModel @ViewModelInject constructor( } fun switchListGrid() { - val intent = Intent(Constants.BROADCAST_ACTION_INTENT_UPDATE_VIEW) - intent.putExtra(Constants.INTENT_EXTRA_KEY_IS_LIST, !isList) - context.sendBroadcast(intent) + LiveEventBus.get(EVENT_UPDATE_VIEW_MODE, Boolean::class.java).post(!isList) } override fun onCleared() { - LiveEventBus.get(EVENT_ORDER_CHANGE, Int::class.java) - .removeObserver(orderChangeObserver) + @Suppress("UNCHECKED_CAST") + LiveEventBus.get(EVENT_ORDER_CHANGE) + .removeObserver(orderChangeObserver as Observer) LiveEventBus.get( EVENT_LIST_GRID_CHANGE, Boolean::class.java ).removeObserver(listGridChangeObserver) } + fun getOrderMap(): HashMap = orderNameMap + companion object { val orderNameMap = hashMapOf( ORDER_DEFAULT_ASC to R.string.sortby_name, @@ -79,7 +91,9 @@ class SortByHeaderViewModel @ViewModelInject constructor( ORDER_MODIFICATION_ASC to R.string.sortby_date, ORDER_MODIFICATION_DESC to R.string.sortby_date, ORDER_SIZE_ASC to R.string.sortby_size, - ORDER_SIZE_DESC to R.string.sortby_size + ORDER_SIZE_DESC to R.string.sortby_size, + ORDER_FAV_ASC to R.string.file_properties_favourite, + ORDER_LABEL_ASC to R.string.title_label ) } } 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 486f04f897a..976927e0ffa 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 @@ -27,6 +27,7 @@ import mega.privacy.android.app.databinding.FragmentAudioBinding import mega.privacy.android.app.di.MegaApi import mega.privacy.android.app.fragments.homepage.* import mega.privacy.android.app.fragments.homepage.BaseNodeItemAdapter.Companion.TYPE_HEADER +import mega.privacy.android.app.globalmanagement.SortOrderManagement import mega.privacy.android.app.lollipop.ManagerActivityLollipop import mega.privacy.android.app.mediaplayer.miniplayer.MiniAudioPlayerController import mega.privacy.android.app.modalbottomsheet.NodeOptionsBottomSheetDialogFragment.MODE1 @@ -66,6 +67,9 @@ class AudioFragment : Fragment(), HomepageSearchable { @Inject lateinit var megaApi: MegaApiAndroid + @Inject + lateinit var sortOrderManagement: SortOrderManagement + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -152,7 +156,7 @@ class AudioFragment : Fragment(), HomepageSearchable { }) sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver { - viewModel.loadAudio(true, it) + viewModel.loadAudio(true) }) sortByHeaderViewModel.listGridChangeEvent.observe( @@ -210,7 +214,7 @@ class AudioFragment : Fragment(), HomepageSearchable { } intent.putExtra(INTENT_EXTRA_KEY_POSITION, index) - intent.putExtra(INTENT_EXTRA_KEY_ORDER_GET_CHILDREN, viewModel.order) + intent.putExtra(INTENT_EXTRA_KEY_ORDER_GET_CHILDREN, sortOrderManagement.getOrderCloud()) intent.putExtra(INTENT_EXTRA_KEY_FILE_NAME, node.name) intent.putExtra(INTENT_EXTRA_KEY_HANDLE, file.handle) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/homepage/audio/AudioViewModel.kt b/app/src/main/java/mega/privacy/android/app/fragments/homepage/audio/AudioViewModel.kt index d58f2f54446..3fe41cbf5fb 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/homepage/audio/AudioViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/homepage/audio/AudioViewModel.kt @@ -6,22 +6,20 @@ import com.jeremyliao.liveeventbus.LiveEventBus import kotlinx.coroutines.launch import mega.privacy.android.app.fragments.homepage.NodeItem import mega.privacy.android.app.fragments.homepage.TypedFilesRepository -import mega.privacy.android.app.utils.Constants +import mega.privacy.android.app.globalmanagement.SortOrderManagement import mega.privacy.android.app.utils.Constants.EVENT_NODES_CHANGE import mega.privacy.android.app.utils.Constants.INVALID_POSITION import mega.privacy.android.app.utils.TextUtil import nz.mega.sdk.MegaApiJava import nz.mega.sdk.MegaApiJava.INVALID_HANDLE -import nz.mega.sdk.MegaApiJava.ORDER_DEFAULT_ASC class AudioViewModel @ViewModelInject constructor( - private val repository: TypedFilesRepository + private val repository: TypedFilesRepository, + private val sortOrderManagement: SortOrderManagement ) : ViewModel() { private var _query = MutableLiveData() - var order: Int = ORDER_DEFAULT_ASC - private set var isList = true var skipNextAutoScroll = false var searchMode = false @@ -36,9 +34,9 @@ class AudioViewModel @ViewModelInject constructor( private var pendingLoad = false val items: LiveData> = _query.switchMap { - if (forceUpdate) { + if (forceUpdate || repository.fileNodeItems.value == null) { viewModelScope.launch { - repository.getFiles(MegaApiJava.FILE_TYPE_AUDIO, order) + repository.getFiles(MegaApiJava.FILE_TYPE_AUDIO, sortOrderManagement.getOrderCloud()) } } else { repository.emitFiles() @@ -91,6 +89,7 @@ class AudioViewModel @ViewModelInject constructor( items.observeForever(loadFinishedObserver) LiveEventBus.get(EVENT_NODES_CHANGE, Boolean::class.java) .observeForever(nodesChangeObserver) + loadAudio(true) } /** @@ -98,9 +97,8 @@ class AudioViewModel @ViewModelInject constructor( * @param forceUpdate True if retrieve all nodes by calling API * , false if filter current nodes by searchQuery */ - fun loadAudio(forceUpdate: Boolean = false, order: Int = this.order) { + fun loadAudio(forceUpdate: Boolean = false) { this.forceUpdate = forceUpdate - this.order = order if (loadInProgress) { pendingLoad = true 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 bd57de117c9..84bb3bdf2c8 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 @@ -39,6 +39,7 @@ import mega.privacy.android.app.modalbottomsheet.UploadBottomSheetDialogFragment import mega.privacy.android.app.utils.* import mega.privacy.android.app.utils.Constants.* import mega.privacy.android.app.utils.MegaNodeUtil.manageTextFileIntent +import mega.privacy.android.app.utils.MegaNodeUtil.onNodeTapped import mega.privacy.android.app.utils.Util.noChangeRecyclerViewItemAnimator import nz.mega.sdk.MegaApiAndroid import nz.mega.sdk.MegaApiJava.INVALID_HANDLE @@ -177,7 +178,7 @@ class DocumentsFragment : Fragment(), HomepageSearchable { }) sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver { - viewModel.loadDocuments(true, it) + viewModel.loadDocuments(true) }) sortByHeaderViewModel.listGridChangeEvent.observe( @@ -477,6 +478,14 @@ class DocumentsFragment : Fragment(), HomepageSearchable { node, if (viewModel.searchMode) DOCUMENTS_SEARCH_ADAPTER else DOCUMENTS_BROWSE_ADAPTER ) + } else { + onNodeTapped( + requireContext(), + node, + { (requireActivity() as ManagerActivityLollipop).saveNodeByTap(it) }, + requireActivity() as ManagerActivityLollipop, + requireActivity() as ManagerActivityLollipop + ) } } diff --git a/app/src/main/java/mega/privacy/android/app/fragments/homepage/documents/DocumentsViewModel.kt b/app/src/main/java/mega/privacy/android/app/fragments/homepage/documents/DocumentsViewModel.kt index 8c09d0a124e..28fa6a999b5 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/homepage/documents/DocumentsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/homepage/documents/DocumentsViewModel.kt @@ -6,20 +6,19 @@ import com.jeremyliao.liveeventbus.LiveEventBus import kotlinx.coroutines.launch import mega.privacy.android.app.fragments.homepage.NodeItem import mega.privacy.android.app.fragments.homepage.TypedFilesRepository -import mega.privacy.android.app.utils.Constants +import mega.privacy.android.app.globalmanagement.SortOrderManagement import mega.privacy.android.app.utils.Constants.EVENT_NODES_CHANGE import mega.privacy.android.app.utils.Constants.INVALID_POSITION import mega.privacy.android.app.utils.TextUtil import nz.mega.sdk.MegaApiJava -import nz.mega.sdk.MegaApiJava.ORDER_DEFAULT_ASC class DocumentsViewModel @ViewModelInject constructor( - private val repository: TypedFilesRepository + private val repository: TypedFilesRepository, + private val sortOrderManagement: SortOrderManagement ) : ViewModel() { private var _query = MutableLiveData() - private var order: Int = ORDER_DEFAULT_ASC var isList = true var skipNextAutoScroll = false var searchMode = false @@ -34,9 +33,9 @@ class DocumentsViewModel @ViewModelInject constructor( private var pendingLoad = false val items: LiveData> = _query.switchMap { - if (forceUpdate) { + if (forceUpdate || repository.fileNodeItems.value == null) { viewModelScope.launch { - repository.getFiles(MegaApiJava.FILE_TYPE_DOCUMENT, order) + repository.getFiles(MegaApiJava.FILE_TYPE_DOCUMENT, sortOrderManagement.getOrderCloud()) } } else { repository.emitFiles() @@ -89,6 +88,7 @@ class DocumentsViewModel @ViewModelInject constructor( items.observeForever(loadFinishedObserver) LiveEventBus.get(EVENT_NODES_CHANGE, Boolean::class.java) .observeForever(nodesChangeObserver) + loadDocuments(true) } /** @@ -96,9 +96,8 @@ class DocumentsViewModel @ViewModelInject constructor( * @param forceUpdate True if retrieve all nodes by calling API * , false if filter current nodes by searchQuery */ - fun loadDocuments(forceUpdate: Boolean = false, order: Int = this.order) { + fun loadDocuments(forceUpdate: Boolean = false) { this.forceUpdate = forceUpdate - this.order = order if (loadInProgress) { pendingLoad = true diff --git a/app/src/main/java/mega/privacy/android/app/fragments/homepage/main/HomepageFragment.kt b/app/src/main/java/mega/privacy/android/app/fragments/homepage/main/HomepageFragment.kt index 1f6bfa9d38b..6d80a2eb958 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/homepage/main/HomepageFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/homepage/main/HomepageFragment.kt @@ -735,6 +735,9 @@ class HomepageFragment : Fragment() { * Showing the full screen mask by adding the mask layout to the window content */ private fun addMask() { + requireActivity().window.statusBarColor = + ContextCompat.getColor(requireContext(), R.color.grey_600_085_dark_grey_070) + windowContent?.addView(fabMaskLayoutDataBinding.root) } @@ -742,6 +745,9 @@ class HomepageFragment : Fragment() { * Removing the full screen mask */ private fun removeMask() { + requireActivity().window.statusBarColor = + ContextCompat.getColor(requireContext(), android.R.color.transparent) + windowContent?.removeView(fabMaskLayoutDataBinding.root) } 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 c8acc96451d..3bdcac6b5b6 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 @@ -30,6 +30,7 @@ import mega.privacy.android.app.databinding.FragmentVideoBinding import mega.privacy.android.app.di.MegaApi import mega.privacy.android.app.fragments.homepage.* import mega.privacy.android.app.fragments.homepage.BaseNodeItemAdapter.Companion.TYPE_HEADER +import mega.privacy.android.app.globalmanagement.SortOrderManagement import mega.privacy.android.app.lollipop.ManagerActivityLollipop import mega.privacy.android.app.mediaplayer.miniplayer.MiniAudioPlayerController import mega.privacy.android.app.modalbottomsheet.NodeOptionsBottomSheetDialogFragment.MODE1 @@ -65,6 +66,9 @@ class VideoFragment : Fragment(), HomepageSearchable { @Inject lateinit var megaApi: MegaApiAndroid + @Inject + lateinit var sortOrderManagement: SortOrderManagement + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -343,7 +347,7 @@ class VideoFragment : Fragment(), HomepageSearchable { }) sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver { - viewModel.loadVideo(true, it) + viewModel.loadVideo(true) }) sortByHeaderViewModel.listGridChangeEvent.observe( @@ -377,7 +381,7 @@ class VideoFragment : Fragment(), HomepageSearchable { } intent.putExtra(INTENT_EXTRA_KEY_POSITION, index) - intent.putExtra(INTENT_EXTRA_KEY_ORDER_GET_CHILDREN, viewModel.order) + intent.putExtra(INTENT_EXTRA_KEY_ORDER_GET_CHILDREN, sortOrderManagement.getOrderCloud()) intent.putExtra(INTENT_EXTRA_KEY_FILE_NAME, node.name) intent.putExtra(INTENT_EXTRA_KEY_HANDLE, file.handle) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/homepage/video/VideoViewModel.kt b/app/src/main/java/mega/privacy/android/app/fragments/homepage/video/VideoViewModel.kt index 9e85bc70213..e1ac25f0ba7 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/homepage/video/VideoViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/homepage/video/VideoViewModel.kt @@ -6,19 +6,19 @@ import com.jeremyliao.liveeventbus.LiveEventBus import kotlinx.coroutines.launch import mega.privacy.android.app.fragments.homepage.NodeItem import mega.privacy.android.app.fragments.homepage.TypedFilesRepository +import mega.privacy.android.app.globalmanagement.SortOrderManagement import mega.privacy.android.app.utils.Constants import mega.privacy.android.app.utils.Constants.EVENT_NODES_CHANGE import mega.privacy.android.app.utils.TextUtil import nz.mega.sdk.MegaApiJava.* class VideoViewModel @ViewModelInject constructor( - private val repository: TypedFilesRepository + private val repository: TypedFilesRepository, + private val sortOrderManagement: SortOrderManagement ) : ViewModel() { private var _query = MutableLiveData() - var order: Int = ORDER_DEFAULT_ASC - private set var isList = true var skipNextAutoScroll = false @@ -34,9 +34,9 @@ class VideoViewModel @ViewModelInject constructor( private var pendingLoad = false val items: LiveData> = _query.switchMap { - if (forceUpdate) { + if (forceUpdate || repository.fileNodeItems.value == null) { viewModelScope.launch { - repository.getFiles(FILE_TYPE_VIDEO, order) + repository.getFiles(FILE_TYPE_VIDEO, sortOrderManagement.getOrderCloud()) } } else { repository.emitFiles() @@ -86,6 +86,7 @@ class VideoViewModel @ViewModelInject constructor( items.observeForever(loadFinishedObserver) LiveEventBus.get(EVENT_NODES_CHANGE, Boolean::class.java) .observeForever(nodesChangeObserver) + loadVideo(true) } /** @@ -93,9 +94,8 @@ class VideoViewModel @ViewModelInject constructor( * @param forceUpdate True if retrieve all nodes by calling API, * false if filter current nodes by searchQuery */ - fun loadVideo(forceUpdate: Boolean = false, order: Int = this.order) { + fun loadVideo(forceUpdate: Boolean = false) { this.forceUpdate = forceUpdate - this.order = order if (loadInProgress) { pendingLoad = true diff --git a/app/src/main/java/mega/privacy/android/app/fragments/managerFragments/LinksFragment.java b/app/src/main/java/mega/privacy/android/app/fragments/managerFragments/LinksFragment.java index a4763a33c53..2caaf023a67 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/managerFragments/LinksFragment.java +++ b/app/src/main/java/mega/privacy/android/app/fragments/managerFragments/LinksFragment.java @@ -110,6 +110,7 @@ public boolean onPrepareActionMode(ActionMode mode, Menu menu) { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); if (megaApi.getRootNode() == null) { return null; @@ -118,7 +119,9 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa View v = getListView(inflater, container); if (adapter == null) { - adapter = new MegaNodeAdapter(context, this, nodes, managerActivity.getParentHandleLinks(), recyclerView, null, LINKS_ADAPTER, ITEM_VIEW_TYPE_LIST); + adapter = new MegaNodeAdapter(context, this, nodes, + managerActivity.getParentHandleLinks(), recyclerView, LINKS_ADAPTER, + ITEM_VIEW_TYPE_LIST, sortByHeaderViewModel); } adapter.setListFragment(recyclerView); diff --git a/app/src/main/java/mega/privacy/android/app/fragments/offline/OfflineFragment.kt b/app/src/main/java/mega/privacy/android/app/fragments/offline/OfflineFragment.kt index 3944833f3a8..eb5aa447942 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/offline/OfflineFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/offline/OfflineFragment.kt @@ -409,7 +409,7 @@ class OfflineFragment : Fragment(), ActionMode.Callback, Scrollable { viewModel.showSortedBy.observe(viewLifecycleOwner, EventObserver { callManager { manager -> - manager.showNewSortByPanel(ORDER_CLOUD) + manager.showNewSortByPanel(ORDER_OFFLINE) } }) @@ -417,12 +417,12 @@ class OfflineFragment : Fragment(), ActionMode.Callback, Scrollable { sortByHeaderViewModel.showDialogEvent.observe(viewLifecycleOwner, EventObserver { callManager { manager -> - manager.showNewSortByPanel(ORDER_CLOUD) + manager.showNewSortByPanel(ORDER_OFFLINE) } }) sortByHeaderViewModel.orderChangeEvent.observe(viewLifecycleOwner, EventObserver { - viewModel.setOrder(it) + viewModel.setOrder(it.third) adapter?.notifyItemChanged(0) }) diff --git a/app/src/main/java/mega/privacy/android/app/fragments/offline/OfflineSortedByViewHolder.kt b/app/src/main/java/mega/privacy/android/app/fragments/offline/OfflineSortedByViewHolder.kt index 7d3b283a173..04e8839c5ba 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/offline/OfflineSortedByViewHolder.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/offline/OfflineSortedByViewHolder.kt @@ -13,7 +13,7 @@ class OfflineSortedByViewHolder( override fun bind(position: Int, node: OfflineNode) { binding.apply { this.orderNameStringId = - SortByHeaderViewModel.orderNameMap[sortByViewModel.order]!! + SortByHeaderViewModel.orderNameMap[sortByViewModel.order.third]!! this.sortByHeaderViewModel = sortByViewModel } } diff --git a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketFragment.kt b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketFragment.kt index 465daccf16e..fd344e6aa40 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsBucketFragment.kt @@ -29,6 +29,7 @@ import mega.privacy.android.app.utils.Constants.* import mega.privacy.android.app.utils.LogUtil.logDebug import mega.privacy.android.app.utils.MegaNodeUtil.manageTextFileIntent import mega.privacy.android.app.utils.MegaNodeUtil.manageURLNode +import mega.privacy.android.app.utils.MegaNodeUtil.onNodeTapped import mega.privacy.android.app.utils.Util.getMediaIntent import mega.privacy.android.app.utils.Util.mutateIconSecondary import nz.mega.sdk.MegaChatApiJava.MEGACHAT_INVALID_HANDLE @@ -192,7 +193,15 @@ class RecentsBucketFragment : BaseFragment() { manageTextFileIntent(requireContext(), node, RECENTS_ADAPTER) } else -> { - download(node.handle) + onNodeTapped( + context, + node, + { + (requireActivity() as ManagerActivityLollipop).saveNodeByTap(it) + }, + (requireActivity() as ManagerActivityLollipop), + (requireActivity() as ManagerActivityLollipop) + ) } } } diff --git a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsFragment.java b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsFragment.java index 8a516aac5f7..d279574d7d2 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsFragment.java +++ b/app/src/main/java/mega/privacy/android/app/fragments/recent/RecentsFragment.java @@ -13,7 +13,6 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; -import android.widget.RelativeLayout; import android.widget.ScrollView; import android.widget.TextView; @@ -63,9 +62,11 @@ import static mega.privacy.android.app.utils.Constants.*; import static mega.privacy.android.app.utils.ContactUtil.*; import static mega.privacy.android.app.utils.FileUtil.*; +import static mega.privacy.android.app.utils.LogUtil.logDebug; import static mega.privacy.android.app.utils.MegaApiUtils.*; import static mega.privacy.android.app.utils.MegaNodeUtil.manageTextFileIntent; import static mega.privacy.android.app.utils.MegaNodeUtil.manageURLNode; +import static mega.privacy.android.app.utils.MegaNodeUtil.onNodeTapped; import static mega.privacy.android.app.utils.SharedPreferenceConstants.HIDE_RECENT_ACTIVITY; import static mega.privacy.android.app.utils.SharedPreferenceConstants.USER_INTERFACE_PREFERENCES; import static mega.privacy.android.app.utils.TextUtil.formatEmptyScreenText; @@ -380,7 +381,7 @@ private long[] getBucketNodeHandles(boolean areImages) { } public void openFile(int index, MegaNode node, boolean isMedia) { - Intent intent = null; + Intent intent; if (MimeTypeList.typeForName(node.getName()).isImage()) { intent = new Intent(context, FullScreenImageViewerLollipop.class); @@ -399,7 +400,7 @@ public void openFile(int index, MegaNode node, boolean isMedia) { } String localPath = getLocalFile(node); - boolean paramsSetSuccessfully = false; + boolean paramsSetSuccessfully; if (isAudioOrVideo(node)) { if (isInternalIntent(node)) { @@ -428,9 +429,10 @@ public void openFile(int index, MegaNode node, boolean isMedia) { if (paramsSetSuccessfully && isOpusFile(node)) { intent.setDataAndType(intent.getData(), "audio/*"); } + + launchIntent(intent, paramsSetSuccessfully, node, index); } else if (MimeTypeList.typeForName(node.getName()).isURL()) { manageURLNode(context, megaApi, node); - return; } else if (MimeTypeList.typeForName(node.getName()).isPdf()) { intent = new Intent(context, PdfViewerActivityLollipop.class); intent.putExtra(INTENT_EXTRA_KEY_INSIDE, true); @@ -443,27 +445,37 @@ public void openFile(int index, MegaNode node, boolean isMedia) { paramsSetSuccessfully = setStreamingIntentParams(context, node, megaApi, intent, (ManagerActivityLollipop) requireActivity()); } + + launchIntent(intent, paramsSetSuccessfully, node, index); } else if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize())) { manageTextFileIntent(requireContext(), node, RECENTS_ADAPTER); - return; + } else { + logDebug("itemClick:isFile:otherOption"); + onNodeTapped(context, node, ((ManagerActivityLollipop) requireActivity())::saveNodeByTap, (ManagerActivityLollipop) requireActivity(), (ManagerActivityLollipop) requireActivity()); } + } + /** + * Launch corresponding intent to open the file based on its type. + * + * @param intent Intent to launch activity. + * @param paramsSetSuccessfully true, if the param is set for the intent successfully; false, otherwise. + * @param node The node to open. + * @param position Thumbnail's position in the list. + */ + private void launchIntent(Intent intent, boolean paramsSetSuccessfully, MegaNode node ,int position) { if (intent != null && !isIntentAvailable(context, intent)) { paramsSetSuccessfully = false; ((ManagerActivityLollipop) context).showSnackbar(SNACKBAR_TYPE, getString(R.string.intent_not_available), -1); } - if (paramsSetSuccessfully) { + if (intent != null && paramsSetSuccessfully) { intent.putExtra(INTENT_EXTRA_KEY_HANDLE, node.getHandle()); - putThumbnailLocation(intent, listView, index, VIEWER_FROM_RECETS, adapter); + putThumbnailLocation(intent, listView, position, VIEWER_FROM_RECETS, adapter); context.startActivity(intent); ((ManagerActivityLollipop) context).overridePendingTransition(0, 0); - return; } - - ((ManagerActivityLollipop) context).saveNodesToDevice(Collections.singletonList(node), - true, false, false, false); } public void setBucketSelected(MegaRecentActionBucket bucketSelected) { diff --git a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/SettingsFileManagementFragment.java b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/SettingsFileManagementFragment.java index 9663daa676a..17ad5893e18 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/SettingsFileManagementFragment.java +++ b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/SettingsFileManagementFragment.java @@ -5,6 +5,8 @@ import android.view.View; import android.view.ViewGroup; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; import androidx.preference.SwitchPreferenceCompat; @@ -19,16 +21,23 @@ import mega.privacy.android.app.listeners.SetAttrUserListener; import mega.privacy.android.app.lollipop.tasks.ManageCacheTask; import mega.privacy.android.app.lollipop.tasks.ManageOfflineTask; +import mega.privacy.android.app.utils.StringResourcesUtils; import nz.mega.sdk.MegaAccountDetails; import static mega.privacy.android.app.constants.SettingsConstants.*; +import static mega.privacy.android.app.utils.AlertDialogUtil.dismissAlertDialogIfExists; +import static mega.privacy.android.app.utils.AlertDialogUtil.isAlertDialogShown; import static mega.privacy.android.app.utils.Constants.INVALID_VALUE; import static mega.privacy.android.app.utils.LogUtil.*; import static mega.privacy.android.app.utils.Util.*; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + @AndroidEntryPoint public class SettingsFileManagementFragment extends SettingsBaseFragment { + private static final String IS_DISABLE_VERSIONS_SHOWN = "IS_DISABLE_VERSIONS_SHOWN"; + @Inject MyAccountInfo myAccountInfo; @@ -43,6 +52,8 @@ public class SettingsFileManagementFragment extends SettingsBaseFragment { private Preference clearVersionsFileManagement; private SwitchPreferenceCompat autoPlaySwitch; + private AlertDialog disableVersionsWarning; + @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { addPreferencesFromResource(R.xml.preferences_file_management); @@ -97,6 +108,24 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { taskGetSizeOffline(); megaApi.getFileVersionsOption(new GetAttrUserListener(context)); + + if (savedInstanceState != null + && savedInstanceState.getBoolean(IS_DISABLE_VERSIONS_SHOWN, false)) { + showWarningDisableVersions(); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putBoolean(IS_DISABLE_VERSIONS_SHOWN, isAlertDialogShown(disableVersionsWarning)); + super.onSaveInstanceState(outState); + } + + @Override + public void onDestroy() { + super.onDestroy(); + + dismissAlertDialogIfExists(disableVersionsWarning); } @Override @@ -143,6 +172,11 @@ public boolean onPreferenceClick(Preference preference) { if (isOffline(context)) return false; + if (!enableVersionsSwitch.isChecked()) { + showWarningDisableVersions(); + return false; + } + megaApi.setFileVersionsOption(!enableVersionsSwitch.isChecked(), new SetAttrUserListener(context)); break; @@ -302,4 +336,15 @@ public void setOnlineOptions(boolean isOnline) { clearVersionsFileManagement.setEnabled(isOnline); clearVersionsFileManagement.setLayoutResource(isOnline ? R.layout.delete_versions_preferences : R.layout.delete_versions_preferences_disabled); } + + private void showWarningDisableVersions() { + enableVersionsSwitch.setChecked(true); + disableVersionsWarning = new MaterialAlertDialogBuilder(requireContext()) + .setTitle(StringResourcesUtils.getString(R.string.disable_versioning_label)) + .setMessage(StringResourcesUtils.getString(R.string.disable_versioning_warning)) + .setPositiveButton(StringResourcesUtils.getString(R.string.verify_2fa_subtitle_diable_2fa), + (dialog, which) -> megaApi.setFileVersionsOption(true, new SetAttrUserListener(context))) + .setNegativeButton(StringResourcesUtils.getString(R.string.general_cancel), null) + .show(); + } } diff --git a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/CookieSettingsFragment.kt b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/CookieSettingsFragment.kt index ffa6928e052..86a2e961650 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/CookieSettingsFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/CookieSettingsFragment.kt @@ -9,40 +9,31 @@ import androidx.core.net.toUri import androidx.fragment.app.activityViewModels import androidx.preference.Preference import androidx.preference.SwitchPreferenceCompat -import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import mega.privacy.android.app.MegaApplication import mega.privacy.android.app.R import mega.privacy.android.app.activities.WebViewActivity -import mega.privacy.android.app.components.ClickableSummarySwitchPreference import mega.privacy.android.app.components.TwoButtonsPreference import mega.privacy.android.app.constants.SettingsConstants.* import mega.privacy.android.app.fragments.settingsFragments.SettingsBaseFragment import mega.privacy.android.app.fragments.settingsFragments.cookie.data.CookieType -import mega.privacy.android.app.fragments.settingsFragments.cookie.data.CookieType.* +import mega.privacy.android.app.fragments.settingsFragments.cookie.data.CookieType.ANALYTICS import mega.privacy.android.app.utils.StringResourcesUtils @AndroidEntryPoint class CookieSettingsFragment : SettingsBaseFragment() { private val viewModel by activityViewModels() - private var isThirdPartyDialogShowing = false private lateinit var acceptCookiesPreference: SwitchPreferenceCompat - private lateinit var preferenceCookiesPreference: SwitchPreferenceCompat private lateinit var analyticsCookiesPreference: SwitchPreferenceCompat - private lateinit var advertisingCookiesPreference: SwitchPreferenceCompat - private lateinit var thirdPartyCookiesPreference: ClickableSummarySwitchPreference private lateinit var policiesPreference: TwoButtonsPreference override fun onCreatePreferences(bundle: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_cookie) acceptCookiesPreference = findPreference(KEY_COOKIE_ACCEPT)!! - preferenceCookiesPreference = findPreference(KEY_COOKIE_PREFERENCE)!! analyticsCookiesPreference = findPreference(KEY_COOKIE_ANALYTICS)!! - advertisingCookiesPreference = findPreference(KEY_COOKIE_ADVERTISING)!! - thirdPartyCookiesPreference = findPreference(KEY_COOKIE_THIRD_PARTY)!! policiesPreference = findPreference(KEY_COOKIE_POLICIES)!! } @@ -65,21 +56,15 @@ class CookieSettingsFragment : SettingsBaseFragment() { private fun setupView() { acceptCookiesPreference.onPreferenceChangeListener = this - preferenceCookiesPreference.onPreferenceChangeListener = this analyticsCookiesPreference.onPreferenceChangeListener = this - advertisingCookiesPreference.onPreferenceChangeListener = this - thirdPartyCookiesPreference.onPreferenceChangeListener = this - thirdPartyCookiesPreference.setSummaryText(StringResourcesUtils.getString(R.string.preference_cookies_thirdparty_summary)) - thirdPartyCookiesPreference.setClickableText(StringResourcesUtils.getString(R.string.action_more_information)) { - showThirdPartyInfoDialog() - } - - policiesPreference.setButton1(StringResourcesUtils.getString(R.string.settings_about_privacy_policy)) { - openBrowser("https://mega.nz/cookie".toUri()) - } - policiesPreference.setButton2(StringResourcesUtils.getString(R.string.settings_about_cookie_policy)) { - openBrowser("https://mega.nz/privacy".toUri()) + policiesPreference.apply { + setButton1(StringResourcesUtils.getString(R.string.settings_about_cookie_policy)) { + openBrowser("https://mega.nz/cookie".toUri()) + } + setButton2(StringResourcesUtils.getString(R.string.settings_about_privacy_policy)) { + openBrowser("https://mega.nz/privacy".toUri()) + } } } @@ -89,15 +74,9 @@ class CookieSettingsFragment : SettingsBaseFragment() { * @param cookies Set of enabled cookies */ private fun showCookies(cookies: Set) { - preferenceCookiesPreference.isChecked = cookies.contains(PREFERENCE) == true analyticsCookiesPreference.isChecked = cookies.contains(ANALYTICS) == true - advertisingCookiesPreference.isChecked = cookies.contains(ADVERTISEMENT) == true - thirdPartyCookiesPreference.isChecked = cookies.contains(THIRDPARTY) == true - acceptCookiesPreference.isChecked = preferenceCookiesPreference.isChecked && - analyticsCookiesPreference.isChecked && - advertisingCookiesPreference.isChecked && - thirdPartyCookiesPreference.isChecked + acceptCookiesPreference.isChecked = analyticsCookiesPreference.isChecked } /** @@ -113,31 +92,12 @@ class CookieSettingsFragment : SettingsBaseFragment() { when (preference?.key) { acceptCookiesPreference.key -> viewModel.toggleCookies(enable) - preferenceCookiesPreference.key -> viewModel.changeCookie(PREFERENCE, enable) analyticsCookiesPreference.key -> viewModel.changeCookie(ANALYTICS, enable) - advertisingCookiesPreference.key -> viewModel.changeCookie(ADVERTISEMENT, enable) - thirdPartyCookiesPreference.key -> viewModel.changeCookie(THIRDPARTY, enable) } return false } - /** - * Show third party cookie information dialog. - */ - private fun showThirdPartyInfoDialog() { - if (!isThirdPartyDialogShowing) { - isThirdPartyDialogShowing = true - - MaterialAlertDialogBuilder(requireContext()) - .setView(R.layout.dialog_cookie_thirdparty) - .setPositiveButton(StringResourcesUtils.getString(R.string.general_yes), null) - .setOnDismissListener { isThirdPartyDialogShowing = false } - .create() - .show() - } - } - /** * Open browser screen to show an Uri * diff --git a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/CookieSettingsViewModel.kt b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/CookieSettingsViewModel.kt index f0336de97a8..240bba5414c 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/CookieSettingsViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/CookieSettingsViewModel.kt @@ -22,7 +22,7 @@ class CookieSettingsViewModel @ViewModelInject constructor( private val enabledCookies = MutableLiveData(mutableSetOf(CookieType.ESSENTIAL)) private val updateResult = MutableLiveData() - private var cookiesSaved = true + private var savedCookiesSize = 1 fun onEnabledCookies(): LiveData> = enabledCookies fun onUpdateResult(): LiveData = updateResult @@ -44,7 +44,6 @@ class CookieSettingsViewModel @ViewModelInject constructor( enabledCookies.value?.remove(cookie) } - cookiesSaved = false enabledCookies.notifyObserver() } @@ -60,14 +59,13 @@ class CookieSettingsViewModel @ViewModelInject constructor( } else { resetCookies() } - - cookiesSaved = false } /** * Check if current cookie settings are saved */ - fun areCookiesSaved(): Boolean = cookiesSaved + fun areCookiesSaved(): Boolean = + savedCookiesSize == enabledCookies.value?.size /** * Save cookie settings to SDK @@ -79,7 +77,6 @@ class CookieSettingsViewModel @ViewModelInject constructor( .subscribeBy( onComplete = { updateResult.value = true - cookiesSaved = true MegaApplication.getInstance().checkEnabledCookies() }, @@ -102,6 +99,7 @@ class CookieSettingsViewModel @ViewModelInject constructor( onSuccess = { settings -> if (!settings.isNullOrEmpty()) { enabledCookies.value = settings.toMutableSet() + savedCookiesSize = settings.size } updateResult.value = true diff --git a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/usecase/CheckCookieBannerEnabledUseCase.kt b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/usecase/CheckCookieBannerEnabledUseCase.kt index 4de9252daa8..fcba890a969 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/usecase/CheckCookieBannerEnabledUseCase.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/usecase/CheckCookieBannerEnabledUseCase.kt @@ -2,8 +2,8 @@ package mega.privacy.android.app.fragments.settingsFragments.cookie.usecase import io.reactivex.rxjava3.core.Single import mega.privacy.android.app.di.MegaApi +import mega.privacy.android.app.listeners.OptionalMegaRequestListenerInterface import mega.privacy.android.app.utils.ErrorUtils.toThrowable -import mega.privacy.android.app.utils.LogUtil.logError import nz.mega.sdk.* import javax.inject.Inject @@ -19,35 +19,17 @@ class CheckCookieBannerEnabledUseCase @Inject constructor( */ fun check(): Single = Single.create { emitter -> - megaApi.getMiscFlags(object : MegaRequestListenerInterface { - override fun onRequestStart(api: MegaApiJava, request: MegaRequest) {} - - override fun onRequestUpdate(api: MegaApiJava, request: MegaRequest) {} - - override fun onRequestFinish( - api: MegaApiJava, - request: MegaRequest, - error: MegaError - ) { - if (emitter.isDisposed) return + megaApi.getMiscFlags(OptionalMegaRequestListenerInterface( + onRequestFinish = { _, error -> + if (emitter.isDisposed) return@OptionalMegaRequestListenerInterface when (error.errorCode) { - MegaError.API_OK, MegaError.API_EACCESS -> { - emitter.onSuccess(api.isCookieBannerEnabled) - } - else -> { + MegaError.API_OK, MegaError.API_EACCESS -> + emitter.onSuccess(megaApi.isCookieBannerEnabled) + else -> emitter.onError(error.toThrowable()) - } } } - - override fun onRequestTemporaryError( - api: MegaApiJava, - request: MegaRequest, - error: MegaError - ) { - logError(error.toThrowable().stackTraceToString()) - } - }) + )) } } diff --git a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/usecase/GetCookieSettingsUseCase.kt b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/usecase/GetCookieSettingsUseCase.kt index f71b75b7ab2..c4c036be439 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/usecase/GetCookieSettingsUseCase.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/usecase/GetCookieSettingsUseCase.kt @@ -3,8 +3,8 @@ package mega.privacy.android.app.fragments.settingsFragments.cookie.usecase import io.reactivex.rxjava3.core.Single import mega.privacy.android.app.di.MegaApi import mega.privacy.android.app.fragments.settingsFragments.cookie.data.CookieType +import mega.privacy.android.app.listeners.OptionalMegaRequestListenerInterface import mega.privacy.android.app.utils.ErrorUtils.toThrowable -import mega.privacy.android.app.utils.LogUtil.logError import nz.mega.sdk.* import java.util.* import javax.inject.Inject @@ -23,17 +23,9 @@ class GetCookieSettingsUseCase @Inject constructor( */ fun get(): Single> = Single.create { emitter -> - megaApi.getCookieSettings(object : MegaRequestListenerInterface { - override fun onRequestStart(api: MegaApiJava, request: MegaRequest) {} - - override fun onRequestUpdate(api: MegaApiJava, request: MegaRequest) {} - - override fun onRequestFinish( - api: MegaApiJava, - request: MegaRequest, - error: MegaError - ) { - if (emitter.isDisposed) return + megaApi.getCookieSettings(OptionalMegaRequestListenerInterface( + onRequestFinish = { request, error -> + if (emitter.isDisposed) return@OptionalMegaRequestListenerInterface when (error.errorCode) { MegaError.API_OK -> { @@ -56,15 +48,7 @@ class GetCookieSettingsUseCase @Inject constructor( } } } - - override fun onRequestTemporaryError( - api: MegaApiJava, - request: MegaRequest, - error: MegaError - ) { - logError(error.toThrowable().stackTraceToString()) - } - }) + )) } /** diff --git a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/usecase/UpdateCookieSettingsUseCase.kt b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/usecase/UpdateCookieSettingsUseCase.kt index 6e3a239304f..5ae98e5b902 100644 --- a/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/usecase/UpdateCookieSettingsUseCase.kt +++ b/app/src/main/java/mega/privacy/android/app/fragments/settingsFragments/cookie/usecase/UpdateCookieSettingsUseCase.kt @@ -2,12 +2,12 @@ package mega.privacy.android.app.fragments.settingsFragments.cookie.usecase import android.content.Intent import io.reactivex.rxjava3.core.Completable -import mega.privacy.android.app.di.MegaApi import mega.privacy.android.app.MegaApplication import mega.privacy.android.app.constants.BroadcastConstants.BROADCAST_ACTION_COOKIE_SETTINGS_SAVED +import mega.privacy.android.app.di.MegaApi import mega.privacy.android.app.fragments.settingsFragments.cookie.data.CookieType +import mega.privacy.android.app.listeners.OptionalMegaRequestListenerInterface import mega.privacy.android.app.utils.ErrorUtils.toThrowable -import mega.privacy.android.app.utils.LogUtil.* import nz.mega.sdk.* import java.util.* import javax.inject.Inject @@ -41,37 +41,16 @@ class UpdateCookieSettingsUseCase @Inject constructor( } val bitSetToDecimal = bitSet.toLongArray().first().toInt() - - megaApi.setCookieSettings(bitSetToDecimal, object : MegaRequestListenerInterface { - override fun onRequestStart(api: MegaApiJava, request: MegaRequest) {} - - override fun onRequestUpdate(api: MegaApiJava, request: MegaRequest) {} - - override fun onRequestFinish( - api: MegaApiJava, - request: MegaRequest, - error: MegaError - ) { - if (emitter.isDisposed) return + megaApi.setCookieSettings(bitSetToDecimal, OptionalMegaRequestListenerInterface( + onRequestFinish = { _, error -> + if (emitter.isDisposed) return@OptionalMegaRequestListenerInterface when (error.errorCode) { - MegaError.API_OK -> { - emitter.onComplete() - } - else -> { - emitter.onError(error.toThrowable()) - } + MegaError.API_OK -> emitter.onComplete() + else -> emitter.onError(error.toThrowable()) } } - - override fun onRequestTemporaryError( - api: MegaApiJava, - request: MegaRequest, - error: MegaError - ) { - logError(error.toThrowable().stackTraceToString()) - } - }) + )) }.doOnComplete { val intent = Intent(BROADCAST_ACTION_COOKIE_SETTINGS_SAVED) MegaApplication.getInstance()?.sendBroadcast(intent) diff --git a/app/src/main/java/mega/privacy/android/app/globalmanagement/SortOrderManagement.kt b/app/src/main/java/mega/privacy/android/app/globalmanagement/SortOrderManagement.kt index a9d929bf6e8..2bbd72fe4b0 100644 --- a/app/src/main/java/mega/privacy/android/app/globalmanagement/SortOrderManagement.kt +++ b/app/src/main/java/mega/privacy/android/app/globalmanagement/SortOrderManagement.kt @@ -1,8 +1,7 @@ package mega.privacy.android.app.globalmanagement import mega.privacy.android.app.DatabaseHandler -import nz.mega.sdk.MegaApiJava.ORDER_DEFAULT_ASC -import nz.mega.sdk.MegaApiJava.ORDER_MODIFICATION_DESC +import nz.mega.sdk.MegaApiJava.* import javax.inject.Inject import javax.inject.Singleton @@ -12,14 +11,23 @@ class SortOrderManagement @Inject constructor( ) { private var orderCloud: Int = ORDER_DEFAULT_ASC - private var orderContacts: Int = ORDER_DEFAULT_ASC private var orderOthers: Int = ORDER_DEFAULT_ASC private var orderCamera: Int = ORDER_MODIFICATION_DESC + /* Same order than orderCloud except when it is ORDER_LABEL_ASC or ORDER_FAV_ASC, then it keeps + the previous selected order */ + private var orderOffline: Int = ORDER_DEFAULT_ASC + init { dbH.preferences?.apply { - preferredSortCloud?.toInt()?.let { orderCloud = it } - preferredSortContacts?.toInt()?.let { orderContacts = it } + preferredSortCloud?.toInt()?.let { + orderCloud = it + + if (canUpdateOfflineOrder(it)) { + orderOffline = it + } + } + preferredSortOthers?.toInt()?.let { orderOthers = it } preferredSortCameraUpload?.toInt()?.let { orderCamera = it } } @@ -30,9 +38,9 @@ class SortOrderManagement @Inject constructor( */ fun resetDefaults() { orderCloud = ORDER_DEFAULT_ASC - orderContacts = ORDER_DEFAULT_ASC orderOthers = ORDER_DEFAULT_ASC orderCamera = ORDER_MODIFICATION_DESC + orderOffline = ORDER_DEFAULT_ASC } fun getOrderCloud(): Int = orderCloud @@ -40,13 +48,10 @@ class SortOrderManagement @Inject constructor( fun setOrderCloud(newOrderCloud: Int) { orderCloud = newOrderCloud dbH.setPreferredSortCloud(orderCloud.toString()) - } - fun getOrderContacts(): Int = orderContacts - - fun setOrderContacts(newOrderContacts: Int) { - orderContacts = newOrderContacts - dbH.setPreferredSortContacts(orderContacts.toString()) + if (canUpdateOfflineOrder(newOrderCloud)) { + orderOffline = newOrderCloud + } } fun getOrderOthers(): Int = orderOthers @@ -62,4 +67,23 @@ class SortOrderManagement @Inject constructor( orderCamera = newOrderCamera dbH.setPreferredSortCameraUpload(orderCamera.toString()) } + + fun getOrderOffline(): Int = orderOffline + + fun setOrderOffline(newOrderOffline: Int) { + orderOffline = newOrderOffline + orderCloud = newOrderOffline + dbH.setPreferredSortCloud(orderCloud.toString()) + } + + /** + * Checks if can update offline order. + * Since offline nodes cannot be ordered by labels and favorites, the offline order will be only + * updated if the new order is different than ORDER_LABEL_ASC and ORDER_FAV_ASC. + * + * @param newOrder New order chosen. + * @return True if can update offline order, false otherwise. + */ + private fun canUpdateOfflineOrder(newOrder: Int): Boolean = + newOrder != ORDER_LABEL_ASC && newOrder != ORDER_FAV_ASC } diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/AddContactActivityLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/AddContactActivityLollipop.java index 4e9c74d3df3..e5c6c0d185c 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/AddContactActivityLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/AddContactActivityLollipop.java @@ -84,8 +84,10 @@ import mega.privacy.android.app.lollipop.adapters.ShareContactsHeaderAdapter; import mega.privacy.android.app.lollipop.controllers.ContactController; import mega.privacy.android.app.lollipop.qrcode.QRCodeActivity; +import mega.privacy.android.app.utils.CallUtil; import mega.privacy.android.app.utils.ColorUtils; import mega.privacy.android.app.utils.HighLightHintHelper; +import mega.privacy.android.app.utils.StringResourcesUtils; import nz.mega.sdk.MegaApiJava; import nz.mega.sdk.MegaChatApi; import nz.mega.sdk.MegaChatApiJava; @@ -3024,12 +3026,16 @@ private void toInviteContact() { startActivityForResult(in, REQUEST_INVITE_CONTACT_FROM_DEVICE); } - private void toStartMeeting(){ - Intent intent = new Intent(); - intent.putExtra(EXTRA_MEETING, true); - setResult(RESULT_OK, intent); - hideKeyboard(addContactActivityLollipop, 0); - finish(); + private void toStartMeeting() { + if (CallUtil.participatingInACall()) { + showConfirmationInACall(this, StringResourcesUtils.getString(R.string.ongoing_call_content), passcodeManagement); + } else { + Intent intent = new Intent(); + intent.putExtra(EXTRA_MEETING, true); + setResult(RESULT_OK, intent); + hideKeyboard(addContactActivityLollipop, 0); + finish(); + } } @Override diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/CloudDriveExplorerFragmentLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/CloudDriveExplorerFragmentLollipop.java index d55a61810ef..9a4ab5acbdc 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/CloudDriveExplorerFragmentLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/CloudDriveExplorerFragmentLollipop.java @@ -11,6 +11,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.ActionMode; import androidx.core.text.HtmlCompat; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -43,13 +44,16 @@ import dagger.hilt.android.AndroidEntryPoint; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.schedulers.Schedulers; +import kotlin.Unit; import mega.privacy.android.app.DatabaseHandler; import mega.privacy.android.app.MegaApplication; import mega.privacy.android.app.MegaPreferences; import mega.privacy.android.app.R; import mega.privacy.android.app.components.CustomizedGridLayoutManager; -import mega.privacy.android.app.components.SimpleDividerItemDecoration; +import mega.privacy.android.app.components.PositionDividerItemDecoration; import mega.privacy.android.app.components.scrollBar.FastScroller; +import mega.privacy.android.app.fragments.homepage.EventObserver; +import mega.privacy.android.app.fragments.homepage.SortByHeaderViewModel; import mega.privacy.android.app.search.callback.SearchActionsCallback; import mega.privacy.android.app.search.usecase.SearchNodesUseCase; import mega.privacy.android.app.globalmanagement.SortOrderManagement; @@ -289,10 +293,27 @@ public void checkScroll () { CLOUD_FRAGMENT); } + /** + * Shows the Sort by panel. + * + * @param unit Unit event. + * @return Null. + */ + private Unit showSortByPanel(Unit unit) { + ((FileExplorerActivityLollipop) context).showSortByPanel(); + return null; + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) { logDebug("onCreateView"); + SortByHeaderViewModel sortByHeaderViewModel = new ViewModelProvider(this) + .get(SortByHeaderViewModel.class); + + sortByHeaderViewModel.getShowDialogEvent().observe(getViewLifecycleOwner(), + new EventObserver<>(this::showSortByPanel)); + View v = inflater.inflate(R.layout.fragment_fileexplorerlist, container, false); Display display = getActivity().getWindowManager().getDefaultDisplay(); @@ -318,7 +339,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle sav v.findViewById(R.id.file_grid_view_browser).setVisibility(View.GONE); mLayoutManager = new LinearLayoutManager(context); recyclerView.setLayoutManager(mLayoutManager); - recyclerView.addItemDecoration(new SimpleDividerItemDecoration(requireContext())); + recyclerView.addItemDecoration(new PositionDividerItemDecoration(requireContext(), getOutMetrics())); } else { recyclerView = v.findViewById(R.id.file_grid_view_browser); @@ -400,7 +421,8 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { } if (adapter == null){ - adapter = new MegaExplorerLollipopAdapter(context, this, nodes, parentHandle, recyclerView, selectFile); + adapter = new MegaExplorerLollipopAdapter(context, this, nodes, parentHandle, + recyclerView, selectFile, sortByHeaderViewModel); } else{ adapter.setListFragment(recyclerView); @@ -408,6 +430,10 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { adapter.setSelectFile(selectFile); } + if (gridLayoutManager != null) { + gridLayoutManager.setSpanSizeLookup(adapter.getSpanSizeLookup(gridLayoutManager.getSpanCount())); + } + recyclerView.setAdapter(adapter); fastScroller.setRecyclerView(recyclerView); setNodes(nodes); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/ContactFileListActivityLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/ContactFileListActivityLollipop.java index 875535e32e1..43a520c48e6 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/ContactFileListActivityLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/ContactFileListActivityLollipop.java @@ -190,9 +190,7 @@ public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.file_explorer_action, menu); - menu.findItem(R.id.cab_menu_sort).setVisible(false); menu.findItem(R.id.cab_menu_search).setVisible(false); - menu.findItem(R.id.cab_menu_grid_list).setVisible(false); createFolderMenuItem = menu.findItem(R.id.cab_menu_create_folder); startConversation = menu.findItem(R.id.cab_menu_new_chat); startConversation.setVisible(false); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/ContactFileListFragmentLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/ContactFileListFragmentLollipop.java index edddb23d15e..7ba51a59659 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/ContactFileListFragmentLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/ContactFileListFragmentLollipop.java @@ -297,7 +297,7 @@ public void onDestroy() { } @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { logDebug("onCreateView"); View v = null; handler = new Handler(); @@ -384,8 +384,8 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { } if (adapter == null) { - adapter = new MegaNodeAdapter(context, this, contactNodes, parentHandle,listView, aB,CONTACT_FILE_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); - + adapter = new MegaNodeAdapter(context, this, contactNodes, parentHandle, + listView, CONTACT_FILE_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); } else { adapter.setNodes(contactNodes); adapter.setParentHandle(parentHandle); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/ContactInfoActivityLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/ContactInfoActivityLollipop.java index 4b3e8ccb393..f9924f34ccc 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/ContactInfoActivityLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/ContactInfoActivityLollipop.java @@ -1100,14 +1100,8 @@ private void setDefaultAvatar() { } private void startingACall(boolean withVideo) { - - if (app.getStorageState() == STORAGE_STATE_PAYWALL) { - showOverDiskQuotaPaywallWarning(); - return; - } - startVideo = withVideo; - if (checkPermissionsCall(this, INVALID_TYPE_PERMISSIONS)) { + if (canCallBeStartedFromContactOption(this, passcodeManagement)) { startCall(); } } @@ -1141,11 +1135,7 @@ public void onClick(View v) { case R.id.chat_contact_properties_chat_video_layout: case R.id.chat_contact_properties_chat_call_layout: - if (isCallOptionEnabled()) { - startingACall(v.getId() == R.id.chat_contact_properties_chat_video_layout); - } else { - showSnackbar(SNACKBAR_TYPE, getString(R.string.not_allowed_to_start_call), MEGACHAT_INVALID_HANDLE); - } + startingACall(v.getId() == R.id.chat_contact_properties_chat_video_layout); break; case R.id.chat_contact_properties_share_contact_layout: { diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/ContactSharedFolderFragment.java b/app/src/main/java/mega/privacy/android/app/lollipop/ContactSharedFolderFragment.java index a9941703069..54ab9bb794f 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/ContactSharedFolderFragment.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/ContactSharedFolderFragment.java @@ -4,6 +4,8 @@ import android.content.res.Resources; import android.os.Bundle; import android.os.Handler; + +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.ActionMode; import androidx.recyclerview.widget.LinearLayoutManager; @@ -50,9 +52,9 @@ public class ContactSharedFolderFragment extends ContactFileBaseFragment { private ActionMode actionMode; Handler handler; - + @Override - public View onCreateView(LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { logDebug("ContactSharedFolderFragment: onCreateView"); handler = new Handler(); View v = null; @@ -83,10 +85,10 @@ public void onClick(View v) { listView.addItemDecoration(new SimpleDividerItemDecoration(context)); listView.setLayoutManager(mLayoutManager); listView.setItemAnimator(noChangeRecyclerViewItemAnimator()); - + if (adapter == null) { - adapter = new MegaNodeAdapter(context,this,contactNodes,-1,listView,aB,CONTACT_SHARED_FOLDER_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); - + adapter = new MegaNodeAdapter(context, this, contactNodes, -1, + listView, CONTACT_SHARED_FOLDER_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); } else { adapter.setNodes(contactNodes); adapter.setParentHandle(-1); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/FileExplorerActivityLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/FileExplorerActivityLollipop.java index 94315f63e2d..6569ce35d40 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/FileExplorerActivityLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/FileExplorerActivityLollipop.java @@ -1,7 +1,9 @@ package mega.privacy.android.app.lollipop; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; @@ -10,6 +12,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.tabs.TabLayout; +import com.jeremyliao.liveeventbus.LiveEventBus; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; @@ -102,6 +105,7 @@ import nz.mega.sdk.MegaUserAlert; import static android.webkit.URLUtil.*; +import static mega.privacy.android.app.constants.EventConstants.EVENT_UPDATE_VIEW_MODE; import static mega.privacy.android.app.modalbottomsheet.ModalBottomSheetUtil.isBottomSheetDialogShown; import static mega.privacy.android.app.utils.AlertsAndWarnings.showOverDiskQuotaPaywallWarning; import static mega.privacy.android.app.utils.ChatUtil.createAttachmentPendingMessage; @@ -199,8 +203,6 @@ public class FileExplorerActivityLollipop extends TransfersManagementActivity private MenuItem createFolderMenuItem; private MenuItem newChatMenuItem; private MenuItem searchMenuItem; - private MenuItem gridListMenuItem; - private MenuItem sortByMenuItem; private boolean isList = true; private FrameLayout cloudDriveFrameLayout; @@ -609,6 +611,7 @@ protected void onCreate(Bundle savedInstanceState) { getWindow().setFlags(WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH, WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH); getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL, WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); + LiveEventBus.get(EVENT_UPDATE_VIEW_MODE, Boolean.class).observe(this, this::refreshViewNodes); } private void afterLoginAndFetch(){ @@ -925,17 +928,6 @@ private boolean isCurrentFragment(int index) { } } - private void setGridListAction () { - if (isList) { - gridListMenuItem.setTitle(R.string.action_grid); - gridListMenuItem.setIcon(R.drawable.ic_thumbnail_view); - } - else { - gridListMenuItem.setTitle(R.string.action_list); - gridListMenuItem.setIcon(R.drawable.ic_list_view); - } - } - private boolean isSearchMultiselect() { if (multiselect) { cDriveExplorer = getCloudExplorerFragment(); @@ -958,16 +950,10 @@ public boolean onCreateOptionsMenu(Menu menu) { searchMenuItem = menu.findItem(R.id.cab_menu_search); createFolderMenuItem = menu.findItem(R.id.cab_menu_create_folder); newChatMenuItem = menu.findItem(R.id.cab_menu_new_chat); - gridListMenuItem = menu.findItem(R.id.cab_menu_grid_list); - sortByMenuItem = menu.findItem(R.id.cab_menu_sort); - setGridListAction(); - sortByMenuItem.setIcon(ContextCompat.getDrawable(this, R.drawable.ic_sort)); searchMenuItem.setVisible(false); createFolderMenuItem.setVisible(false); newChatMenuItem.setVisible(false); - gridListMenuItem.setVisible(true); - sortByMenuItem.setVisible(false); searchView = (SearchView) searchMenuItem.getActionView(); @@ -987,8 +973,6 @@ public boolean onMenuItemActionExpand(MenuItem item) { if (isSearchMultiselect()) { hideTabs(true, isCloudVisible() ? CLOUD_FRAGMENT : INCOMING_FRAGMENT); - gridListMenuItem.setVisible(false); - sortByMenuItem.setVisible(false); } else { hideTabs(true, CHAT_FRAGMENT); chatExplorer = getChatExplorerFragment(); @@ -1102,7 +1086,6 @@ public boolean onPrepareOptionsMenu(Menu menu) { if(index==0){ if(isChatFirst){ - gridListMenuItem.setVisible(false); searchMenuItem.setVisible(true); createFolderMenuItem.setVisible(false); newChatMenuItem.setVisible(false); @@ -1112,7 +1095,6 @@ public boolean onPrepareOptionsMenu(Menu menu) { setCreateFolderVisibility(); newChatMenuItem.setVisible(false); if (multiselect) { - sortByMenuItem.setVisible(true); cDriveExplorer = getCloudExplorerFragment(); searchMenuItem.setVisible(cDriveExplorer != null && !cDriveExplorer.isFolderEmpty()); } @@ -1154,7 +1136,6 @@ else if(index==1){ } newChatMenuItem.setVisible(false); if (multiselect) { - sortByMenuItem.setVisible(true); searchMenuItem.setVisible(iSharesExplorer != null && !iSharesExplorer.isFolderEmpty()); } } @@ -1190,7 +1171,6 @@ else if(index==2){ newChatMenuItem.setVisible(false); } else{ - gridListMenuItem.setVisible(false); searchMenuItem.setVisible(true); createFolderMenuItem.setVisible(false); newChatMenuItem.setVisible(false); @@ -1235,15 +1215,10 @@ else if (importFileF) { break; } case CHAT_FRAGMENT:{ - gridListMenuItem.setVisible(false); newChatMenuItem.setVisible(false); searchMenuItem.setVisible(true); break; } - case IMPORT_FRAGMENT: { - gridListMenuItem.setVisible(false); - break; - } } } } @@ -2363,14 +2338,6 @@ public boolean onOptionsItemSelected(MenuItem item) { } break; } - case R.id.cab_menu_grid_list:{ - refreshViewNodes(); - break; - } - case R.id.cab_menu_sort:{ - showSortByPanel(); - break; - } } return super.onOptionsItemSelected(item); } @@ -2734,57 +2701,21 @@ public void refreshOrderNodes (int order) { } } - private void refreshViewNodes () { - isList = !isList; - dbH.setPreferredViewList(isList); - updateManagerView(); - refreshView(); - supportInvalidateOptionsMenu(); - } + private void refreshViewNodes (boolean isList) { + this.isList = isList; - private void refreshView () { - if (viewPagerExplorer != null && tabShown != NO_TABS) { - cDriveExplorer = (CloudDriveExplorerFragmentLollipop) getSupportFragmentManager().findFragmentByTag(getFragmentTag(R.id.explorer_tabs_pager, 0)); - iSharesExplorer = (IncomingSharesExplorerFragmentLollipop) getSupportFragmentManager().findFragmentByTag(getFragmentTag(R.id.explorer_tabs_pager, 0)); - } else { - cDriveExplorer = getCloudExplorerFragment(); - iSharesExplorer = getIncomingExplorerFragment(); - } + cDriveExplorer = getCloudExplorerFragment(); + iSharesExplorer = getIncomingExplorerFragment(); if (cDriveExplorer != null) { - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.detach(cDriveExplorer); - ft.attach(cDriveExplorer); - ft.commitAllowingStateLoss(); + getSupportFragmentManager().beginTransaction().detach(cDriveExplorer).commitNowAllowingStateLoss(); + getSupportFragmentManager().beginTransaction().attach(cDriveExplorer).commitNowAllowingStateLoss(); } if (iSharesExplorer != null) { - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.detach(iSharesExplorer); - ft.attach(iSharesExplorer); - ft.commitAllowingStateLoss(); + getSupportFragmentManager().beginTransaction().detach(iSharesExplorer).commitNowAllowingStateLoss(); + getSupportFragmentManager().beginTransaction().attach(iSharesExplorer).commitNowAllowingStateLoss(); } - - if (viewPagerExplorer != null && tabShown != NO_TABS) { - if (currentAction.equals(ACTION_SELECT_FOLDER_TO_SHARE) || - currentAction.equals(ACTION_UPLOAD_TO_CLOUD)) { - updateAdapterExplorer(false, DEFAULT_TAB_TO_REMOVE); - } else if (currentAction.equals(ACTION_MULTISELECT_FILE) || - currentAction.equals(ACTION_PICK_MOVE_FOLDER) || - currentAction.equals(ACTION_PICK_COPY_FOLDER) || - currentAction.equals(ACTION_CHOOSE_MEGA_FOLDER_SYNC) || - currentAction.equals(ACTION_PICK_IMPORT_FOLDER)) { - updateAdapterExplorer(false, CHAT_TAB); - } else if (isChatFirst) { - updateAdapterExplorer(true, DEFAULT_TAB_TO_REMOVE); - } - } - } - - private void updateManagerView () { - Intent intent = new Intent(BROADCAST_ACTION_INTENT_UPDATE_VIEW); - intent.putExtra("isList", isList); - sendBroadcast(intent); } public void collapseSearchView () { diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/FileLinkActivityLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/FileLinkActivityLollipop.java index 058b71aacb7..f16839dafd9 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/FileLinkActivityLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/FileLinkActivityLollipop.java @@ -939,7 +939,7 @@ public void successfulCopy(){ startIntent.setAction(ACTION_OPEN_FOLDER); startIntent.putExtra("PARENT_HANDLE", toHandle); startIntent.putExtra("offline_adapter", false); - startIntent.putExtra("locationFileInfo", true); + startIntent.putExtra(INTENT_EXTRA_KEY_LOCATION_FILE_INFO, true); startIntent.putExtra("fragmentHandle", fragmentHandle); } startActivity(startIntent); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/FolderLinkActivityLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/FolderLinkActivityLollipop.java index 08f6262bb90..58fba19ebf3 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/FolderLinkActivityLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/FolderLinkActivityLollipop.java @@ -987,7 +987,9 @@ else if (pN.isFile()){ } if (adapterList == null){ - adapterList = new MegaNodeAdapter(this, null, nodes, parentHandle, listView, aB, FOLDER_LINK_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); + adapterList = new MegaNodeAdapter(this, null, nodes, + parentHandle, listView, FOLDER_LINK_ADAPTER, + MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); } else{ adapterList.setParentHandle(parentHandle); @@ -1616,7 +1618,7 @@ public void successfulCopy(){ startIntent.setAction(ACTION_OPEN_FOLDER); startIntent.putExtra("PARENT_HANDLE", toHandle); startIntent.putExtra("offline_adapter", false); - startIntent.putExtra("locationFileInfo", true); + startIntent.putExtra(INTENT_EXTRA_KEY_LOCATION_FILE_INFO, true); startIntent.putExtra("fragmentHandle", fragmentHandle); } startActivity(startIntent); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/FullScreenImageViewerLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/FullScreenImageViewerLollipop.java index ab77dfd5d92..1d3a714a8ce 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/FullScreenImageViewerLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/FullScreenImageViewerLollipop.java @@ -8,7 +8,6 @@ import android.content.IntentFilter; import android.content.res.Configuration; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.Handler; @@ -38,6 +37,7 @@ import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -261,7 +261,7 @@ public boolean onCreateOptionsMenu(Menu menu) { fromIncoming = nC.nodeComesFromIncoming(megaApi.getNodeByHandle(imageHandles.get(positionG))); } - if(adapterType != OFFLINE_ADAPTER) { + if(adapterType != OFFLINE_ADAPTER && adapterType != ZIP_ADAPTER) { shareIcon.setVisible(showShareOption(adapterType, isFolderLink, imageHandles.get(positionG))); } @@ -287,7 +287,7 @@ public boolean onCreateOptionsMenu(Menu menu) { removeIcon.setVisible(false); chatIcon.setVisible(false); } else if (adapterType == RUBBISH_BIN_ADAPTER - || megaApi.isInRubbish(megaApi.getNodeByHandle(imageHandles.get(positionG)))){ + || (!imageHandles.isEmpty() && megaApi.isInRubbish(megaApi.getNodeByHandle(imageHandles.get(positionG))))){ renameIcon.setVisible(false); moveIcon.setVisible(false); copyIcon .setVisible(false); @@ -654,7 +654,7 @@ public boolean onOptionsItemSelected(MenuItem item) { if (adapterType == OFFLINE_ADAPTER) { shareFile(this, getOfflineFile(this, mOffListImages.get(positionG))); } else if (adapterType == ZIP_ADAPTER) { - shareFile(this, zipFiles.get(positionG)); + shareFile(this, new File(paths.get(positionG))); } else if (adapterType == FILE_LINK_ADAPTER) { shareLink(this, url); } else { @@ -915,8 +915,6 @@ protected void onCreate(Bundle savedInstanceState) { File offlineDirectory = new File(currentImage.getParent()); paths.clear(); - int imageNumber = 0; - int index = 0; File[] fList = offlineDirectory.listFiles(); if(fList == null) { @@ -925,35 +923,36 @@ protected void onCreate(Bundle savedInstanceState) { finish(); return; } - for (int i=0; i { - String name1 = z1.getName(); - String name2 = z2.getName(); - int res = String.CASE_INSENSITIVE_ORDER.compare(name1, name2); - if (res == 0) { - res = name1.compareTo(name2); - } - return res; - }); - logDebug("SIZE: " + zipFiles.size()); - for (File f : zipFiles){ - logDebug("F: " + f.getAbsolutePath()); - if (MimeTypeList.typeForName(f.getName()).isImage()){ - paths.add(f.getAbsolutePath()); - if (index == positionG && savedInstanceState == null){ - positionG = imageNumber; - } - imageNumber++; - } - index++; - } + zipFiles.addAll(Arrays.asList(fList)); + // Put folders at first. + Collections.sort(zipFiles, (z1, z2) -> { + if ((z1.isDirectory() && z2.isDirectory()) || (!z1.isDirectory() && !z2.isDirectory())) { + return 0; + } + return -1; + }); + logDebug("SIZE: " + zipFiles.size()); + + // Add all images into a list first. + for (File f : zipFiles) { + if (MimeTypeList.typeForName(f.getName()).isImage()) { + paths.add(f.getAbsolutePath()); + } + } - if(paths.size() == 0) finish(); + if (paths.isEmpty()) finish(); - if(positionG >= paths.size()) positionG = 0; + // Adjust positionG + int nonImageNum = 0; + for (int i = 0; i < positionG; i++) { + String name = zipFiles.get(i).getName(); + if (!MimeTypeList.typeForName(name).isImage()) { + nonImageNum++; + } + } + positionG -= nonImageNum; + if(positionG >= paths.size()) positionG = 0; adapterOffline = new MegaOfflineFullScreenImageAdapterLollipop(this, fullScreenImageViewer, paths, true); fileNameTextView.setText(new File(paths.get(positionG)).getName()); @@ -1029,7 +1028,7 @@ else if (adapterType == RECENTS_ADAPTER || adapterType == RECENTS_BUCKET_ADAPTER getImageHandles(nodes, savedInstanceState); } - if (adapterType == OFFLINE_ADAPTER) { + if (adapterType == OFFLINE_ADAPTER || adapterType == ZIP_ADAPTER) { viewPager.setAdapter(adapterOffline); } else { diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/IncomingSharesExplorerFragmentLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/IncomingSharesExplorerFragmentLollipop.java index f5b21a1b8e8..84935b184d9 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/IncomingSharesExplorerFragmentLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/IncomingSharesExplorerFragmentLollipop.java @@ -28,6 +28,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.ActionMode; import androidx.core.text.HtmlCompat; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -44,13 +45,16 @@ import dagger.hilt.android.AndroidEntryPoint; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.schedulers.Schedulers; +import kotlin.Unit; import mega.privacy.android.app.MegaApplication; import mega.privacy.android.app.MegaPreferences; import mega.privacy.android.app.R; import mega.privacy.android.app.components.CustomizedGridLayoutManager; import mega.privacy.android.app.components.NewGridRecyclerView; -import mega.privacy.android.app.components.SimpleDividerItemDecoration; +import mega.privacy.android.app.components.PositionDividerItemDecoration; import mega.privacy.android.app.components.scrollBar.FastScroller; +import mega.privacy.android.app.fragments.homepage.EventObserver; +import mega.privacy.android.app.fragments.homepage.SortByHeaderViewModel; import mega.privacy.android.app.search.callback.SearchActionsCallback; import mega.privacy.android.app.search.usecase.SearchNodesUseCase; import mega.privacy.android.app.globalmanagement.SortOrderManagement; @@ -268,10 +272,27 @@ public void checkScroll() { FileExplorerActivityLollipop.INCOMING_FRAGMENT); } + /** + * Shows the Sort by panel. + * + * @param unit Unit event. + * @return Null. + */ + private Unit showSortByPanel(Unit unit) { + ((FileExplorerActivityLollipop) context).showSortByPanel(); + return null; + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) { logDebug("onCreateView"); + SortByHeaderViewModel sortByHeaderViewModel = new ViewModelProvider(this) + .get(SortByHeaderViewModel.class); + + sortByHeaderViewModel.getShowDialogEvent().observe(getViewLifecycleOwner(), + new EventObserver<>(this::showSortByPanel)); + Display display = getActivity().getWindowManager().getDefaultDisplay(); outMetrics = new DisplayMetrics(); @@ -300,7 +321,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle sav v.findViewById(R.id.file_grid_view_browser).setVisibility(View.GONE); mLayoutManager = new LinearLayoutManager(context); recyclerView.setLayoutManager(mLayoutManager); - recyclerView.addItemDecoration(new SimpleDividerItemDecoration(requireContext())); + recyclerView.addItemDecoration(new PositionDividerItemDecoration(requireContext(), getOutMetrics())); } else { recyclerView = (NewGridRecyclerView) v.findViewById(R.id.file_grid_view_browser); @@ -342,7 +363,8 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { getNodes(); if (adapter == null){ - adapter = new MegaExplorerLollipopAdapter(context, this, nodes, parentHandle, recyclerView, selectFile); + adapter = new MegaExplorerLollipopAdapter(context, this, nodes, parentHandle, + recyclerView, selectFile, sortByHeaderViewModel); } else{ adapter.setListFragment(recyclerView); @@ -350,6 +372,10 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { adapter.setSelectFile(selectFile); } + if (gridLayoutManager != null) { + gridLayoutManager.setSpanSizeLookup(adapter.getSpanSizeLookup(gridLayoutManager.getSpanCount())); + } + recyclerView.setAdapter(adapter); fastScroller.setRecyclerView(recyclerView); setNodes(nodes); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/ManagerActivityLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/ManagerActivityLollipop.java index 0ba602e54ae..97edbc29e6f 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/ManagerActivityLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/ManagerActivityLollipop.java @@ -106,6 +106,7 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.Calendar; +import java.util.Collections; import java.util.List; import java.util.ListIterator; import java.util.Locale; @@ -656,7 +657,6 @@ private enum HomepageScreen { private MenuItem enableSelectMenuItem; private MenuItem selectMenuItem; private MenuItem unSelectMenuItem; - private MenuItem thumbViewMenuItem; private MenuItem sortByMenuItem; private MenuItem helpMenuItem; private MenuItem doNotDisturbMenuItem; @@ -878,16 +878,6 @@ public void onReceive(Context context, Intent intent) { } }; - private BroadcastReceiver receiverUpdateView = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if (intent != null) { - updateView(intent.getBooleanExtra("isList", true)); - supportInvalidateOptionsMenu(); - } - } - }; - private BroadcastReceiver receiverCUAttrChanged = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -1464,7 +1454,10 @@ protected void onCreate(Bundle savedInstanceState) { new IntentFilter(BROADCAST_ACTION_INTENT_CU_ATTR_CHANGE)); registerReceiver(receiverUpdateOrder, new IntentFilter(BROADCAST_ACTION_INTENT_UPDATE_ORDER)); - registerReceiver(receiverUpdateView, new IntentFilter(BROADCAST_ACTION_INTENT_UPDATE_VIEW)); + + LiveEventBus.get(EVENT_UPDATE_VIEW_MODE, Boolean.class) + .observe(this, this::updateView); + registerReceiver(chatArchivedReceiver, new IntentFilter(BROADCAST_ACTION_INTENT_CHAT_ARCHIVED)); LiveEventBus.get(EVENT_REFRESH_PHONE_NUMBER, Boolean.class) @@ -1602,8 +1595,6 @@ else if (newIntent.getAction().equals(ACTION_CANCEL_CAM_SYNC)){ LiveEventBus.get(EVENT_LIST_GRID_CHANGE, Boolean.class).post(isList); - LiveEventBus.get(EVENT_ORDER_CHANGE, Integer.class).post(sortOrderManagement.getOrderCloud()); - handler = new Handler(); logDebug("Set view"); @@ -2177,7 +2168,7 @@ else if(getIntent().getAction().equals(ACTION_CHANGE_MAIL)){ } else if (getIntent().getAction().equals(ACTION_OPEN_FOLDER)) { logDebug("Open after LauncherFileExplorerActivityLollipop "); - boolean locationFileInfo = getIntent().getBooleanExtra("locationFileInfo", false); + boolean locationFileInfo = getIntent().getBooleanExtra(INTENT_EXTRA_KEY_LOCATION_FILE_INFO, false); long handleIntent = getIntent().getLongExtra("PARENT_HANDLE", -1); if (getIntent().getBooleanExtra(SHOW_MESSAGE_UPLOAD_STARTED, false)) { @@ -2249,7 +2240,8 @@ else if(getIntent().getAction().equals(ACTION_IPC)){ logDebug("IPC link - go to received request in Contacts"); markNotificationsSeen(true); navigateToContactRequests(); - selectDrawerItemPending=false; + getIntent().setAction(null); + setIntent(null); } else if(getIntent().getAction().equals(ACTION_CHAT_NOTIFICATION_MESSAGE)){ logDebug("Chat notitificacion received"); @@ -3301,7 +3293,7 @@ else if (getIntent().getAction().equals(ACTION_REFRESH_API_SERVER)){ } else if (getIntent().getAction().equals(ACTION_OPEN_FOLDER)) { logDebug("Open after LauncherFileExplorerActivityLollipop "); - long handleIntent = getIntent().getLongExtra("PARENT_HANDLE", -1); + long handleIntent = getIntent().getLongExtra(INTENT_EXTRA_KEY_PARENT_HANDLE, -1); if (getIntent().getBooleanExtra(SHOW_MESSAGE_UPLOAD_STARTED, false)) { int numberUploads = getIntent().getIntExtra(NUMBER_UPLOADS, 1); @@ -3490,7 +3482,6 @@ protected void onDestroy(){ unregisterReceiver(updateMyAccountReceiver); unregisterReceiver(networkReceiver); unregisterReceiver(receiverUpdateOrder); - unregisterReceiver(receiverUpdateView); unregisterReceiver(chatArchivedReceiver); LiveEventBus.get(EVENT_REFRESH_PHONE_NUMBER, Boolean.class) .removeObserver(refreshAddPhoneNumberButtonObserver); @@ -4535,6 +4526,9 @@ public void selectDrawerItemLollipop(DrawerItem item) { bottomNavigationCurrentItem = CLOUD_DRIVE_BNV; } setBottomNavigationMenuItemChecked(CLOUD_DRIVE_BNV); + if (getIntent() != null && getIntent().getBooleanExtra(INTENT_EXTRA_KEY_LOCATION_FILE_INFO, false)) { + fbFLol.refreshNodes(); + } logDebug("END for Cloud Drive"); break; } @@ -5253,7 +5247,6 @@ public boolean onQueryTextChange(String newText) { enableSelectMenuItem = menu.findItem(R.id.action_enable_select); selectMenuItem = menu.findItem(R.id.action_select); unSelectMenuItem = menu.findItem(R.id.action_unselect); - thumbViewMenuItem = menu.findItem(R.id.action_grid); sortByMenuItem = menu.findItem(R.id.action_menu_sort_by); helpMenuItem = menu.findItem(R.id.action_menu_help); doNotDisturbMenuItem = menu.findItem(R.id.action_menu_do_not_disturb); @@ -5302,10 +5295,7 @@ public boolean onQueryTextChange(String newText) { createFolderMenuItem.setVisible(true); if (isCloudAdded() && fbFLol.getItemCount() > 0) { - thumbViewMenuItem.setVisible(true); - setGridListIcon(); searchMenuItem.setVisible(true); - sortByMenuItem.setVisible(true); } break; case HOMEPAGE: @@ -5316,10 +5306,7 @@ public boolean onQueryTextChange(String newText) { break; case RUBBISH_BIN: if (getRubbishBinFragment() != null && rubbishBinFLol.getItemCount() > 0) { - thumbViewMenuItem.setVisible(true); - setGridListIcon(); clearRubbishBinMenuitem.setVisible(true); - sortByMenuItem.setVisible(true); searchMenuItem.setVisible(true); } break; @@ -5329,10 +5316,7 @@ public boolean onQueryTextChange(String newText) { case INBOX: if (getInboxFragment() != null && iFLol.getItemCount() > 0) { selectMenuItem.setVisible(true); - sortByMenuItem.setVisible(true); searchMenuItem.setVisible(true); - thumbViewMenuItem.setVisible(true); - setGridListIcon(); } break; @@ -5341,9 +5325,6 @@ public boolean onQueryTextChange(String newText) { addMenuItem.setEnabled(true); if (isIncomingAdded() && inSFLol.getItemCount() > 0) { - thumbViewMenuItem.setVisible(true); - setGridListIcon(); - sortByMenuItem.setVisible(true); searchMenuItem.setVisible(true); if (parentHandleIncoming != INVALID_HANDLE) { @@ -5370,14 +5351,10 @@ public boolean onQueryTextChange(String newText) { } if (isOutgoingAdded() && outSFLol.getItemCount() > 0) { - thumbViewMenuItem.setVisible(true); - setGridListIcon(); - sortByMenuItem.setVisible(true); searchMenuItem.setVisible(true); } } else if (getTabItemShares() == LINKS_TAB && isLinksAdded()) { if (isLinksAdded() && lF.getItemCount() > 0) { - sortByMenuItem.setVisible(true); searchMenuItem.setVisible(true); } } @@ -5390,9 +5367,6 @@ public boolean onQueryTextChange(String newText) { } else if (getSearchFragment() != null && getSearchFragment().getNodes() != null && getSearchFragment().getNodes().size() > 0) { - sortByMenuItem.setVisible(true); - thumbViewMenuItem.setVisible(true); - setGridListIcon(); } break; @@ -5527,16 +5501,6 @@ public F getFragmentByType(Class fragmentClass) { return null; } - private void setGridListIcon() { - if (isList){ - thumbViewMenuItem.setTitle(getString(R.string.action_grid)); - thumbViewMenuItem.setIcon(R.drawable.ic_thumbnail_view); - } - else{ - thumbViewMenuItem.setIcon(R.drawable.ic_list_view); - } - } - @Override public boolean onOptionsItemSelected(MenuItem item) { logDebug("onOptionsItemSelected"); @@ -5844,44 +5808,18 @@ else if(viewPagerShares.getCurrentItem()==1){ return true; } - case R.id.action_grid: { - logDebug("action_grid selected"); - - updateView(!isList); - supportInvalidateOptionsMenu(); - return true; - } - case R.id.action_menu_clear_rubbish_bin:{ - showClearRubbishBinDialog(); - return true; - } - case R.id.action_menu_sort_by:{ - int orderType; - - switch (drawerItem) { - case PHOTOS: - orderType = ORDER_CAMERA; - break; - - default: - if (drawerItem == DrawerItem.SHARED_ITEMS - && getTabItemShares() == INCOMING_TAB && deepBrowserTreeIncoming == 0) { - showNewSortByPanel(ORDER_OTHERS, true); - return true; - } + case R.id.action_menu_clear_rubbish_bin: + showClearRubbishBinDialog(); + return true; - if (drawerItem == DrawerItem.SHARED_ITEMS - && getTabItemShares() == OUTGOING_TAB && deepBrowserTreeOutgoing == 0) { - orderType = ORDER_OTHERS; - } else { - orderType = ORDER_CLOUD; - } + case R.id.action_menu_sort_by: + if (drawerItem == DrawerItem.PHOTOS) { + showNewSortByPanel(ORDER_CAMERA); } - showNewSortByPanel(orderType); - return true; - } - case R.id.action_menu_help:{ + return true; + + case R.id.action_menu_help:{ Intent intent = new Intent(); intent.setAction(Intent.ACTION_VIEW); intent.addCategory(Intent.CATEGORY_BROWSABLE); @@ -5944,7 +5882,6 @@ private void hideItemsWhenSearchSelected() { helpMenuItem.setVisible(false); inviteMenuItem.setVisible(false); selectMenuItem.setVisible(false); - thumbViewMenuItem.setVisible(false); searchMenuItem.setVisible(false); openMeetingMenuItem.setVisible(false); } @@ -7149,6 +7086,7 @@ private void setupFabs() { fabs.add(fabMaskLayout.findViewById(R.id.text_chat)); fabs.add(fabMaskLayout.findViewById(R.id.text_meeting)); + fabMaskLayout.setOnClickListener(l-> fabMainClickCallback()); fabMaskButton.setOnClickListener(l-> fabMainClickCallback()); fabMaskLayout.findViewById(R.id.fab_chat).setOnClickListener(l -> { @@ -7202,6 +7140,7 @@ private void expandFab() { * Showing the full screen mask by adding the mask layout to the window content */ private void addMask() { + getWindow().setStatusBarColor(ContextCompat.getColor(this, R.color.grey_600_085_dark_grey_070)); windowContent.addView(fabMaskLayout); } @@ -7209,6 +7148,7 @@ private void addMask() { * Removing the full screen mask */ private void removeMask() { + getWindow().setStatusBarColor(ContextCompat.getColor(this, android.R.color.transparent)); windowContent.removeView(fabMaskLayout); } @@ -7283,7 +7223,7 @@ private void showIn(ArrayList fabs) { @Override public void onJoinMeeting() { if(CallUtil.participatingInACall()){ - showConfirmationInACall(this); + showConfirmationInACall(this, StringResourcesUtils.getString(R.string.text_join_call), passcodeManagement); } else { showOpenLinkDialog(); } @@ -7292,7 +7232,7 @@ public void onJoinMeeting() { @Override public void onCreateMeeting() { if(CallUtil.participatingInACall()){ - showConfirmationInACall(this); + showConfirmationInACall(this, StringResourcesUtils.getString(R.string.ongoing_call_content), passcodeManagement); } else { openMeetingToCreate(this); } @@ -7342,6 +7282,17 @@ public void saveNodesToDevice(List nodes, boolean highPriority, boolea nodeSaver.saveNodes(nodes, highPriority, isFolderLink, fromMediaViewer, fromChat); } + /** + * Upon a node is tapped, if it cannot be previewed in-app, + * then download it first, this download will be marked as "download by tap". + * + * @param node Node to be downloaded. + */ + public Unit saveNodeByTap(MegaNode node) { + nodeSaver.saveNodes(Collections.singletonList(node), true, false, false, false, false, true); + return null; + } + /** * Save nodes to gallery. * @@ -7671,8 +7622,12 @@ public void showOfflineFileInfo(MegaOffline node) { } public void showMeetingOptionsPanel(){ - bottomSheetDialogFragment = new MeetingBottomSheetDialogFragment(); - bottomSheetDialogFragment.show(getSupportFragmentManager(), MeetingBottomSheetDialogFragment.TAG); + if (CallUtil.participatingInACall()) { + showConfirmationInACall(this, StringResourcesUtils.getString(R.string.ongoing_call_content), passcodeManagement); + } else { + bottomSheetDialogFragment = new MeetingBottomSheetDialogFragment(); + bottomSheetDialogFragment.show(getSupportFragmentManager(), MeetingBottomSheetDialogFragment.TAG); + } } /** @@ -7833,8 +7788,6 @@ private void refreshSharesPageAdapter() { } public void refreshCloudOrder(int order) { - LiveEventBus.get(EVENT_ORDER_CHANGE, Integer.class).post(order); - //Refresh Cloud Fragment refreshCloudDrive(); @@ -11555,6 +11508,13 @@ public void openTransferLocation(AndroidCompletedTransfer transfer) { openFullscreenOfflineFragment( removeInitialOfflinePath(transfer.getPath()) + SEPARATOR); } else { + File file = new File(transfer.getPath()); + + if (!isFileAvailable(file)) { + showSnackbar(SNACKBAR_TYPE, StringResourcesUtils.getString(R.string.location_not_exist), MEGACHAT_INVALID_HANDLE); + return; + } + Intent intent = new Intent(this, FileStorageActivityLollipop.class); intent.setAction(FileStorageActivityLollipop.Mode.BROWSE_FILES.getAction()); intent.putExtra(FileStorageActivityLollipop.EXTRA_PATH, transfer.getPath()); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/PdfViewerActivityLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/PdfViewerActivityLollipop.java index 0472ac81866..8646397464f 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/PdfViewerActivityLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/PdfViewerActivityLollipop.java @@ -1048,7 +1048,7 @@ public boolean onCreateOptionsMenu(Menu menu) { saveForOfflineMenuItem.setVisible(false); chatRemoveMenuItem.setVisible(false); } else if (type == RUBBISH_BIN_ADAPTER - || megaApi.isInRubbish(megaApi.getNodeByHandle(handle))) { + || (megaApi != null && megaApi.isInRubbish(megaApi.getNodeByHandle(handle)))) { shareMenuItem.setVisible(false); getlinkMenuItem.setVisible(false); removelinkMenuItem.setVisible(false); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/ZipBrowserActivityLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/ZipBrowserActivityLollipop.java index a72a2c0529b..d9a0fbb93e0 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/ZipBrowserActivityLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/ZipBrowserActivityLollipop.java @@ -295,7 +295,6 @@ public void onCreate (Bundle savedInstanceState){ e.printStackTrace(); } - orderZips(); if (adapterList == null){ adapterList = new ZipListAdapterLollipop(this, recyclerView, aB, zipNodes, currentFolder); } @@ -304,18 +303,6 @@ public void onCreate (Bundle savedInstanceState){ observeDragSupportEvents(this, recyclerView, VIEWER_FROM_ZIP_BROWSER); } - - void orderZips () { - Collections.sort(zipNodes, (z1, z2) -> { - String name1 = z1.getName(); - String name2 = z2.getName(); - int res = String.CASE_INSENSITIVE_ORDER.compare(name1, name2); - if (res == 0) { - res = name1.compareTo(name2); - } - return res; - }); - } @Override public boolean onOptionsItemSelected(MenuItem item) { @@ -334,19 +321,14 @@ public void openFile(int position) { if (dbH == null){ dbH = DatabaseHandler.getDbHandler(getApplicationContext()); } - - String downloadLocationDefaultPath = getDownloadLocation(); - - String absolutePath= downloadLocationDefaultPath+"/"+currentPath; - if(!folderzipped){ - logDebug("folderzipped = " + folderzipped); - int index = pathZip.lastIndexOf("."); - absolutePath = pathZip.substring(0, index); - absolutePath = absolutePath +"/"+currentPath; - } - else{ - logDebug("folderzipped = " + folderzipped); - } + + int indexDot = pathZip.lastIndexOf("."); + String absolutePath = pathZip.substring(0, indexDot); + if (!folderzipped) { + absolutePath = absolutePath + "/" + currentPath; + } else { + absolutePath = absolutePath + "/" + currentPath.substring(currentPath.lastIndexOf("/")); + } logDebug("The absolutePath of the file to open is: " + absolutePath); @@ -529,7 +511,6 @@ public void itemClick(int position) { depth=depth+1; listDirectory(currentPath); this.setFolder(currentPath); - orderZips(); adapterList.setNodes(zipNodes); } else{ diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/adapters/MegaExplorerLollipopAdapter.java b/app/src/main/java/mega/privacy/android/app/lollipop/adapters/MegaExplorerLollipopAdapter.java index 69ace4e5f63..8585fa6c133 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/adapters/MegaExplorerLollipopAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/adapters/MegaExplorerLollipopAdapter.java @@ -7,10 +7,10 @@ import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import android.util.DisplayMetrics; import android.util.SparseBooleanArray; -import android.util.TypedValue; import android.view.Display; import android.view.LayoutInflater; import android.view.View; @@ -32,6 +32,8 @@ import mega.privacy.android.app.R; import mega.privacy.android.app.components.NewGridRecyclerView; import mega.privacy.android.app.components.scrollBar.SectionTitleProvider; +import mega.privacy.android.app.databinding.SortByHeaderBinding; +import mega.privacy.android.app.fragments.homepage.SortByHeaderViewModel; import mega.privacy.android.app.lollipop.CloudDriveExplorerFragmentLollipop; import mega.privacy.android.app.lollipop.FileExplorerActivityLollipop; import mega.privacy.android.app.lollipop.IncomingSharesExplorerFragmentLollipop; @@ -44,6 +46,9 @@ import nz.mega.sdk.MegaShare; import nz.mega.sdk.MegaUser; +import static mega.privacy.android.app.lollipop.adapters.MegaNodeAdapter.ITEM_VIEW_TYPE_GRID; +import static mega.privacy.android.app.lollipop.adapters.MegaNodeAdapter.ITEM_VIEW_TYPE_HEADER; +import static mega.privacy.android.app.lollipop.adapters.MegaNodeAdapter.ITEM_VIEW_TYPE_LIST; import static mega.privacy.android.app.utils.Constants.*; import static mega.privacy.android.app.utils.FileUtil.*; import static mega.privacy.android.app.utils.LogUtil.*; @@ -52,6 +57,9 @@ import static mega.privacy.android.app.utils.TimeUtils.*; import static mega.privacy.android.app.utils.Util.*; import static mega.privacy.android.app.utils.ContactUtil.*; +import static nz.mega.sdk.MegaApiJava.INVALID_HANDLE; + +import org.jetbrains.annotations.NotNull; public class MegaExplorerLollipopAdapter extends RecyclerView.Adapter implements View.OnClickListener, View.OnLongClickListener, SectionTitleProvider, RotatableAdapter { public static int MAX_WIDTH_FILENAME_LAND=500; @@ -81,6 +89,8 @@ public class MegaExplorerLollipopAdapter extends RecyclerView.Adapter _nodes, long _parentHandle, RecyclerView listView, boolean selectFile){ + public MegaExplorerLollipopAdapter(Context _context, Object fragment, ArrayList _nodes, + long _parentHandle, RecyclerView listView, boolean selectFile, + SortByHeaderViewModel sortByHeaderViewModel){ this.context = _context; this.nodes = _nodes; this.parentHandle = _parentHandle; @@ -137,6 +168,7 @@ public MegaExplorerLollipopAdapter(Context _context, Object fragment, ArrayList< this.imageIds = new ArrayList(); this.names = new ArrayList(); this.fragment = fragment; + this.sortByViewModel = sortByHeaderViewModel; if (megaApi == null){ megaApi = ((MegaApplication) ((Activity)context).getApplication()).getMegaApi(); @@ -159,6 +191,13 @@ public int getItemCount() { return nodes.size(); } + @Override + public int getItemViewType(int position) { + return !nodes.isEmpty() && position == 0 ? ITEM_VIEW_TYPE_HEADER + : ((FileExplorerActivityLollipop) context).isList() + ? ITEM_VIEW_TYPE_LIST : ITEM_VIEW_TYPE_GRID; + } + public Object getItem(int position) { return nodes.get(position); } @@ -172,7 +211,7 @@ public long getItemId(int position) { public ViewHolderExplorerLollipop onCreateViewHolder(ViewGroup parent, int viewType) { View v; - if (((FileExplorerActivityLollipop) context).isList()) { + if (viewType == ITEM_VIEW_TYPE_LIST) { logDebug("onCreateViewHolder list"); v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_file_explorer, parent, false); ViewHolderListExplorerLollipop holder = new ViewHolderListExplorerLollipop(v); @@ -186,7 +225,7 @@ public ViewHolderExplorerLollipop onCreateViewHolder(ViewGroup parent, int viewT holder.textViewFileName.setTag(holder); v.setTag(holder); return holder; - } else { + } else if (viewType == ITEM_VIEW_TYPE_GRID){ logDebug("onCreateViewHolder grid"); v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_file_explorer_grid, parent, false); ViewHolderGridExplorerLollipop holder = new ViewHolderGridExplorerLollipop(v); @@ -208,18 +247,28 @@ public ViewHolderExplorerLollipop onCreateViewHolder(ViewGroup parent, int viewT v.setTag(holder); return holder; - } + } else { + SortByHeaderBinding binding = SortByHeaderBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new ViewHolderSortBy(binding); + } } @Override public void onBindViewHolder(ViewHolderExplorerLollipop holder, int position){ - if (((FileExplorerActivityLollipop) context).isList()) { - ViewHolderListExplorerLollipop holderList = (ViewHolderListExplorerLollipop) holder; - onBindViewHolderList(holderList, position); - } - else { - ViewHolderGridExplorerLollipop holderGrid = (ViewHolderGridExplorerLollipop) holder; - onBindViewHolderGrid(holderGrid, position); + switch (getItemViewType(position)) { + case ITEM_VIEW_TYPE_HEADER: + ((ViewHolderSortBy) holder).bind(sortByViewModel); + break; + + case ITEM_VIEW_TYPE_LIST: + ViewHolderListExplorerLollipop holderList = (ViewHolderListExplorerLollipop) holder; + onBindViewHolderList(holderList, position); + break; + + case ITEM_VIEW_TYPE_GRID: + ViewHolderGridExplorerLollipop holderGrid = (ViewHolderGridExplorerLollipop) holder; + onBindViewHolderGrid(holderGrid, position); + break; } } @@ -720,7 +769,13 @@ else if (fragment instanceof IncomingSharesExplorerFragmentLollipop) { private ArrayList insertPlaceHolderNode(ArrayList nodes) { if (((FileExplorerActivityLollipop) context).isList()) { - placeholderCount = 0; + if (!nodes.isEmpty()) { + placeholderCount = 1; + nodes.add(0, null); + } else { + placeholderCount = 0; + } + return nodes; } @@ -744,6 +799,11 @@ private ArrayList insertPlaceHolderNode(ArrayList nodes) { } } + if (!nodes.isEmpty()) { + placeholderCount++; + nodes.add(0, null); + } + return nodes; } @@ -777,4 +837,13 @@ public String getSectionTitle(int position) { public void setListFragment(RecyclerView listFragment) { this.listFragment = listFragment; } + + @NotNull + public final GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) { + return (GridLayoutManager.SpanSizeLookup) (new GridLayoutManager.SpanSizeLookup() { + public int getSpanSize(int position) { + return getItemViewType(position) == ITEM_VIEW_TYPE_HEADER ? spanCount : 1; + } + }); + } } diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/adapters/MegaNodeAdapter.java b/app/src/main/java/mega/privacy/android/app/lollipop/adapters/MegaNodeAdapter.java index 4e7d13c0820..e873d01dc54 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/adapters/MegaNodeAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/adapters/MegaNodeAdapter.java @@ -11,6 +11,7 @@ import androidx.core.content.ContextCompat; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; import android.graphics.drawable.Drawable; import android.util.DisplayMetrics; @@ -28,6 +29,7 @@ import android.widget.RelativeLayout; import android.widget.TextView; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; @@ -42,6 +44,8 @@ import mega.privacy.android.app.components.NewGridRecyclerView; import mega.privacy.android.app.components.dragger.DragThumbnailGetter; import mega.privacy.android.app.components.scrollBar.SectionTitleProvider; +import mega.privacy.android.app.databinding.SortByHeaderBinding; +import mega.privacy.android.app.fragments.homepage.SortByHeaderViewModel; import mega.privacy.android.app.fragments.managerFragments.LinksFragment; import mega.privacy.android.app.components.twemoji.EmojiTextView; import mega.privacy.android.app.lollipop.ContactFileListActivityLollipop; @@ -82,6 +86,7 @@ public class MegaNodeAdapter extends RecyclerView.Adapter insertPlaceHolderNode(ArrayList nodes) { if (adapterType == ITEM_VIEW_TYPE_LIST) { - placeholderCount = 0; + if (shouldShowSortByHeader(nodes)) { + placeholderCount = 1; + nodes.add(0, null); + } else { + placeholderCount = 0; + } + return nodes; } @@ -460,14 +487,65 @@ private ArrayList insertPlaceHolderNode(ArrayList nodes) { } } + if (shouldShowSortByHeader(nodes)) { + placeholderCount++; + nodes.add(0, null); + } + return nodes; } - public MegaNodeAdapter(Context _context, Object fragment, ArrayList _nodes, long _parentHandle, RecyclerView recyclerView, ActionBar aB, int type, int adapterType) { + /** + * Checks if should show sort by header. + * It should show the header if the list of nodes is not empty and if the adapter is not: + * FOLDER_LINK_ADAPTER, CONTACT_SHARED_FOLDER_ADAPTER or CONTACT_FILE_ADAPTER. + * + * @param nodes List of nodes to check if is empty or not. + * @return True if should show the sort by header, false otherwise. + */ + private boolean shouldShowSortByHeader(ArrayList nodes) { + return !nodes.isEmpty() && type != FOLDER_LINK_ADAPTER + && type != CONTACT_SHARED_FOLDER_ADAPTER && type != CONTACT_FILE_ADAPTER; + } + + @NotNull + public final GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final int spanCount) { + return (GridLayoutManager.SpanSizeLookup) (new GridLayoutManager.SpanSizeLookup() { + public int getSpanSize(int position) { + return getItemViewType(position) == ITEM_VIEW_TYPE_HEADER ? spanCount : 1; + } + }); + } + + public MegaNodeAdapter(Context context, Object fragment, ArrayList nodes, + long parentHandle, RecyclerView recyclerView, int type, int adapterType) { + initAdapter(context, fragment, nodes, parentHandle, recyclerView, type, adapterType); + } - this.context = _context; - this.nodes = _nodes; - this.parentHandle = _parentHandle; + public MegaNodeAdapter(Context context, Object fragment, ArrayList nodes, + long parentHandle, RecyclerView recyclerView, int type, int adapterType, + SortByHeaderViewModel sortByHeaderViewModel) { + initAdapter(context, fragment, nodes, parentHandle, recyclerView, type, adapterType); + this.sortByViewModel = sortByHeaderViewModel; + } + + /** + * Initializes the principal properties of the adapter. + * + * @param context Current Context. + * @param fragment Current Fragment. + * @param nodes List of nodes. + * @param parentHandle Current parent handle. + * @param recyclerView View in which the adapter will be set. + * @param type Fragment adapter type. + * @param adapterType List or grid adapter type. + */ + private void initAdapter(Context context, Object fragment, ArrayList nodes, + long parentHandle, RecyclerView recyclerView, int type, int adapterType) { + + this.context = context; + this.nodes = nodes; + this.parentHandle = parentHandle; this.type = type; this.adapterType = adapterType; this.fragment = fragment; @@ -503,6 +581,7 @@ public MegaNodeAdapter(Context _context, Object fragment, ArrayList _n megaApi = ((MegaApplication)((Activity)context).getApplication()) .getMegaApi(); } + } public void setNodes(ArrayList nodes) { @@ -654,7 +733,8 @@ public ViewHolderBrowser onCreateViewHolder(ViewGroup parent, int viewType) { return holderGrid; } else { - return null; + SortByHeaderBinding binding = SortByHeaderBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new ViewHolderSortBy(binding); } } @@ -662,13 +742,22 @@ public ViewHolderBrowser onCreateViewHolder(ViewGroup parent, int viewType) { public void onBindViewHolder(ViewHolderBrowser holder, int position) { logDebug("Position: " + position); - if (adapterType == ITEM_VIEW_TYPE_LIST) { - ViewHolderBrowserList holderList = (ViewHolderBrowserList)holder; - onBindViewHolderList(holderList,position); - } else if (adapterType == ITEM_VIEW_TYPE_GRID) { - ViewHolderBrowserGrid holderGrid = (ViewHolderBrowserGrid)holder; - onBindViewHolderGrid(holderGrid,position); + switch (getItemViewType(position)) { + case ITEM_VIEW_TYPE_HEADER: + ((ViewHolderSortBy) holder).bind(sortByViewModel); + break; + + case ITEM_VIEW_TYPE_LIST: + ViewHolderBrowserList holderList = (ViewHolderBrowserList) holder; + onBindViewHolderList(holderList, position); + break; + + case ITEM_VIEW_TYPE_GRID: + ViewHolderBrowserGrid holderGrid = (ViewHolderBrowserGrid) holder; + onBindViewHolderGrid(holderGrid, position); + break; } + reSelectUnhandledNode(); } @@ -840,11 +929,9 @@ public void onBindViewHolderList(ViewHolderBrowserList holder,int position) { holder.textViewFileName.setText(node.getName()); holder.textViewFileSize.setText(""); - holder.imageFavourite.setVisibility(type != RUBBISH_BIN_ADAPTER - && type != FOLDER_LINK_ADAPTER && node.isFavourite() ? View.VISIBLE : View.GONE); + holder.imageFavourite.setVisibility(type != FOLDER_LINK_ADAPTER && node.isFavourite() ? View.VISIBLE : View.GONE); - if (type != RUBBISH_BIN_ADAPTER && type != FOLDER_LINK_ADAPTER - && node.getLabel() != MegaNode.NODE_LBL_UNKNOWN) { + if (type != FOLDER_LINK_ADAPTER && node.getLabel() != MegaNode.NODE_LBL_UNKNOWN) { Drawable drawable = MegaNodeUtil.getNodeLabelDrawable(node.getLabel(), holder.itemView.getResources()); holder.imageLabel.setImageDrawable(drawable); holder.imageLabel.setVisibility(View.VISIBLE); @@ -1053,7 +1140,12 @@ public int getItemCount() { @Override public int getItemViewType(int position) { - return adapterType; + return !nodes.isEmpty() && position == 0 + && type != FOLDER_LINK_ADAPTER + && type != CONTACT_SHARED_FOLDER_ADAPTER + && type != CONTACT_FILE_ADAPTER + ? ITEM_VIEW_TYPE_HEADER + : adapterType; } public Object getItem(int position) { diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/controllers/AccountController.java b/app/src/main/java/mega/privacy/android/app/lollipop/controllers/AccountController.java index 69e4491366e..8d1d7fc524d 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/controllers/AccountController.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/controllers/AccountController.java @@ -70,20 +70,7 @@ public class AccountController { public AccountController(Context context){ logDebug("AccountController created"); this.context = context; - - if (megaApi == null){ - if (context instanceof MegaApplication){ - megaApi = ((MegaApplication)context).getMegaApi(); - } - else{ - megaApi = ((MegaApplication) ((Activity)context).getApplication()).getMegaApi(); - } - } - } - - public AccountController(Context context, MegaApiAndroid megaApi){ - this.context = context; - this.megaApi = megaApi; + this.megaApi = MegaApplication.getInstance().getMegaApi(); } public void resetPass(String myEmail){ diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/FileBrowserFragmentLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/FileBrowserFragmentLollipop.java index 1880b1fe8a7..27b66055a74 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/FileBrowserFragmentLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/FileBrowserFragmentLollipop.java @@ -17,6 +17,7 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.ActionMode; import androidx.core.text.HtmlCompat; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -39,22 +40,16 @@ import android.widget.TextView; import android.widget.Toast; -import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Stack; import javax.inject.Inject; import dagger.hilt.android.AndroidEntryPoint; +import kotlin.Unit; import mega.privacy.android.app.DatabaseHandler; import mega.privacy.android.app.MegaApplication; import mega.privacy.android.app.MegaPreferences; @@ -62,17 +57,22 @@ import mega.privacy.android.app.R; import mega.privacy.android.app.components.CustomizedGridLayoutManager; import mega.privacy.android.app.components.NewGridRecyclerView; -import mega.privacy.android.app.components.SimpleDividerItemDecoration; +import mega.privacy.android.app.components.PositionDividerItemDecoration; import mega.privacy.android.app.components.scrollBar.FastScroller; +import mega.privacy.android.app.fragments.homepage.EventObserver; +import mega.privacy.android.app.fragments.homepage.SortByHeaderViewModel; import mega.privacy.android.app.globalmanagement.SortOrderManagement; import mega.privacy.android.app.lollipop.FullScreenImageViewerLollipop; import mega.privacy.android.app.lollipop.ManagerActivityLollipop; import mega.privacy.android.app.lollipop.PdfViewerActivityLollipop; +import mega.privacy.android.app.lollipop.ZipBrowserActivityLollipop; import mega.privacy.android.app.lollipop.adapters.MegaNodeAdapter; import mega.privacy.android.app.lollipop.controllers.NodeController; import mega.privacy.android.app.utils.CloudStorageOptionControlUtil; import mega.privacy.android.app.utils.ColorUtils; +import mega.privacy.android.app.utils.FileUtil; import mega.privacy.android.app.utils.MegaNodeUtil; +import mega.privacy.android.app.utils.SDCardUtils; import mega.privacy.android.app.utils.StringResourcesUtils; import nz.mega.sdk.MegaApiAndroid; import nz.mega.sdk.MegaChatApiAndroid; @@ -89,6 +89,7 @@ import static mega.privacy.android.app.utils.MegaNodeUtil.allHaveOwnerAccess; import static mega.privacy.android.app.utils.MegaNodeUtil.manageTextFileIntent; import static mega.privacy.android.app.utils.MegaNodeUtil.manageURLNode; +import static mega.privacy.android.app.utils.MegaNodeUtil.onNodeTapped; import static mega.privacy.android.app.utils.TimeUtils.*; import static mega.privacy.android.app.utils.Util.*; @@ -151,6 +152,17 @@ public void activateActionMode(){ } } + /** + * Shows the Sort by panel. + * + * @param unit Unit event. + * @return Null. + */ + private Unit showSortByPanel(Unit unit) { + ((ManagerActivityLollipop) context).showNewSortByPanel(ORDER_CLOUD); + return null; + } + private class ActionBarCallBack implements ActionMode.Callback { @Override @@ -463,6 +475,12 @@ public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bun return null; } + SortByHeaderViewModel sortByHeaderViewModel = new ViewModelProvider(this) + .get(SortByHeaderViewModel.class); + + sortByHeaderViewModel.getShowDialogEvent().observe(getViewLifecycleOwner(), + new EventObserver<>(this::showSortByPanel)); + logDebug("Fragment ADDED"); if (megaApi == null) { @@ -482,15 +500,7 @@ public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bun display.getMetrics(outMetrics); density = getResources().getDisplayMetrics().density; - if (((ManagerActivityLollipop) context).getParentHandleBrowser() == -1 || ((ManagerActivityLollipop) context).getParentHandleBrowser() == megaApi.getRootNode().getHandle()) { - logWarning("After consulting... the parent keeps -1 or ROOTNODE: " + ((ManagerActivityLollipop) context).getParentHandleBrowser()); - - nodes = megaApi.getChildren(megaApi.getRootNode(), sortOrderManagement.getOrderCloud()); - } else { - MegaNode parentNode = megaApi.getNodeByHandle(((ManagerActivityLollipop) context).getParentHandleBrowser()); - - nodes = megaApi.getChildren(parentNode, sortOrderManagement.getOrderCloud()); - } + getNodes(); ((ManagerActivityLollipop) context).setToolbarTitle(); ((ManagerActivityLollipop) context).supportInvalidateOptionsMenu(); @@ -510,7 +520,7 @@ public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bun recyclerView.setLayoutManager(mLayoutManager); recyclerView.setHasFixedSize(true); recyclerView.setItemAnimator(noChangeRecyclerViewItemAnimator()); - recyclerView.addItemDecoration(new SimpleDividerItemDecoration(requireContext())); + recyclerView.addItemDecoration(new PositionDividerItemDecoration(requireContext(), getOutMetrics())); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { @@ -524,7 +534,9 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { emptyTextViewFirst = v.findViewById(R.id.file_list_empty_text_first); if (adapter == null){ - adapter = new MegaNodeAdapter(context, this, nodes, ((ManagerActivityLollipop)context).getParentHandleBrowser(), recyclerView, aB, FILE_BROWSER_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); + adapter = new MegaNodeAdapter(context, this, nodes, + ((ManagerActivityLollipop) context).getParentHandleBrowser(), recyclerView, + FILE_BROWSER_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST, sortByHeaderViewModel); } else{ adapter.setParentHandle(((ManagerActivityLollipop)context).getParentHandleBrowser()); @@ -576,13 +588,17 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { emptyTextViewFirst = v.findViewById(R.id.file_grid_empty_text_first); if (adapter == null) { - adapter = new MegaNodeAdapter(context,this,nodes,((ManagerActivityLollipop)context).getParentHandleBrowser(),recyclerView,aB,FILE_BROWSER_ADAPTER,MegaNodeAdapter.ITEM_VIEW_TYPE_GRID); + adapter = new MegaNodeAdapter(context, this, nodes, + ((ManagerActivityLollipop) context).getParentHandleBrowser(), recyclerView, + FILE_BROWSER_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_GRID, sortByHeaderViewModel); } else { adapter.setParentHandle(((ManagerActivityLollipop)context).getParentHandleBrowser()); adapter.setListFragment(recyclerView); adapter.setAdapterType(MegaNodeAdapter.ITEM_VIEW_TYPE_GRID); } + gridLayoutManager.setSpanSizeLookup(adapter.getSpanSizeLookup(gridLayoutManager.getSpanCount())); + adapter.setMultipleSelect(false); recyclerView.setAdapter(adapter); @@ -646,6 +662,25 @@ public void onDestroy() { super.onDestroy(); } + private void getNodes() { + long parentHandleBrowser = ((ManagerActivityLollipop) context).getParentHandleBrowser(); + if (parentHandleBrowser == -1 || parentHandleBrowser == megaApi.getRootNode().getHandle()) { + logWarning("After consulting... the parent keeps -1 or ROOTNODE: " + parentHandleBrowser); + + nodes = megaApi.getChildren(megaApi.getRootNode(), sortOrderManagement.getOrderCloud()); + } else { + MegaNode parentNode = megaApi.getNodeByHandle(parentHandleBrowser); + nodes = megaApi.getChildren(parentNode, sortOrderManagement.getOrderCloud()); + } + } + + public void refreshNodes(){ + if (adapter != null) { + getNodes(); + adapter.setNodes(nodes); + } + } + public void openFile(MegaNode node, int position) { if (MimeTypeList.typeForName(node.getName()).isImage()) { Intent intent = new Intent(context, FullScreenImageViewerLollipop.class); @@ -837,13 +872,11 @@ public void openFile(MegaNode node, int position) { ((ManagerActivityLollipop) context).overridePendingTransition(0, 0); } else if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize())) { manageTextFileIntent(context, node, FILE_BROWSER_ADAPTER); - } else { - logDebug("itemClick:isFile:otherOption"); - ((ManagerActivityLollipop) context).saveNodesToDevice( - Collections.singletonList(node), - true, false, false, false); - } - } + } else { + logDebug("itemClick:isFile:otherOption"); + onNodeTapped(context, node, ((ManagerActivityLollipop) context)::saveNodeByTap, (ManagerActivityLollipop) context, (ManagerActivityLollipop) context); + } + } public void itemClick(int position) { logDebug("item click position: " + position); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/InboxFragmentLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/InboxFragmentLollipop.java index a049e7bdc6c..917a28c236a 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/InboxFragmentLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/InboxFragmentLollipop.java @@ -16,6 +16,7 @@ import androidx.core.content.FileProvider; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.ActionMode; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -53,6 +54,7 @@ import javax.inject.Inject; import dagger.hilt.android.AndroidEntryPoint; +import kotlin.Unit; import mega.privacy.android.app.DatabaseHandler; import mega.privacy.android.app.MegaApplication; import mega.privacy.android.app.MegaPreferences; @@ -60,7 +62,9 @@ import mega.privacy.android.app.R; import mega.privacy.android.app.components.CustomizedGridLayoutManager; import mega.privacy.android.app.components.NewGridRecyclerView; -import mega.privacy.android.app.components.SimpleDividerItemDecoration; +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.globalmanagement.SortOrderManagement; import mega.privacy.android.app.lollipop.FullScreenImageViewerLollipop; import mega.privacy.android.app.lollipop.ManagerActivityLollipop; @@ -82,6 +86,7 @@ import static mega.privacy.android.app.utils.MegaApiUtils.*; import static mega.privacy.android.app.utils.MegaNodeUtil.manageTextFileIntent; import static mega.privacy.android.app.utils.MegaNodeUtil.manageURLNode; +import static mega.privacy.android.app.utils.MegaNodeUtil.onNodeTapped; import static mega.privacy.android.app.utils.Util.*; @AndroidEntryPoint @@ -361,6 +366,17 @@ public void selectAll(){ new Handler(Looper.getMainLooper()).post(() -> updateActionModeTitle()); } } + + /** + * Shows the Sort by panel. + * + * @param unit Unit event. + * @return Null. + */ + private Unit showSortByPanel(Unit unit) { + ((ManagerActivityLollipop) context).showNewSortByPanel(ORDER_CLOUD); + return null; + } @Override public void onCreate (Bundle savedInstanceState){ @@ -393,6 +409,12 @@ public void checkScroll () { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { logDebug("onCreateView"); + SortByHeaderViewModel sortByHeaderViewModel = new ViewModelProvider(this) + .get(SortByHeaderViewModel.class); + + sortByHeaderViewModel.getShowDialogEvent().observe(getViewLifecycleOwner(), + new EventObserver<>(this::showSortByPanel)); + display = ((Activity)context).getWindowManager().getDefaultDisplay(); outMetrics = new DisplayMetrics (); display.getMetrics(outMetrics); @@ -424,14 +446,13 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa View v = inflater.inflate(R.layout.fragment_inboxlist, container, false); recyclerView = (RecyclerView) v.findViewById(R.id.inbox_list_view); -// recyclerView.addItemDecoration(new SimpleDividerItemDecoration(context, outMetrics)); mLayoutManager = new LinearLayoutManager(context); //Add bottom padding for recyclerView like in other fragments. recyclerView.setPadding(0, 0, 0, scaleHeightPx(85, outMetrics)); recyclerView.setClipToPadding(false); recyclerView.setLayoutManager(mLayoutManager); recyclerView.setItemAnimator(noChangeRecyclerViewItemAnimator()); - recyclerView.addItemDecoration(new SimpleDividerItemDecoration(requireContext())); + recyclerView.addItemDecoration(new PositionDividerItemDecoration(requireContext(), getOutMetrics())); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { @@ -445,13 +466,13 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { emptyTextViewFirst = (TextView) v.findViewById(R.id.inbox_list_empty_text_first); if (adapter == null){ - adapter = new MegaNodeAdapter(context, this, nodes, ((ManagerActivityLollipop) context).getParentHandleInbox(), recyclerView, null, INBOX_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); + adapter = new MegaNodeAdapter(context, this, nodes, + ((ManagerActivityLollipop) context).getParentHandleInbox(), + recyclerView, INBOX_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST, sortByHeaderViewModel); } else{ adapter.setParentHandle(((ManagerActivityLollipop) context).getParentHandleInbox()); -// addSectionTitle(nodes,MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); adapter.setListFragment(recyclerView); -// adapter.setNodes(nodes); adapter.setAdapterType(MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); } @@ -484,7 +505,9 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { emptyTextViewFirst = (TextView) v.findViewById(R.id.inbox_grid_empty_text_first); if (adapter == null){ - adapter = new MegaNodeAdapter(context, this, nodes, ((ManagerActivityLollipop) context).getParentHandleInbox(), recyclerView, null, INBOX_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_GRID); + adapter = new MegaNodeAdapter(context, this, nodes, + ((ManagerActivityLollipop) context).getParentHandleInbox(), recyclerView, + INBOX_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_GRID, sortByHeaderViewModel); } else{ adapter.setParentHandle(((ManagerActivityLollipop) context).getParentHandleInbox()); @@ -492,6 +515,8 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { adapter.setAdapterType(MegaNodeAdapter.ITEM_VIEW_TYPE_GRID); } + gridLayoutManager.setSpanSizeLookup(adapter.getSpanSizeLookup(gridLayoutManager.getSpanCount())); + recyclerView.setAdapter(adapter); setNodes(nodes); @@ -693,9 +718,7 @@ public void openFile(MegaNode node, int position) { manageTextFileIntent(requireContext(), node, INBOX_ADAPTER); } else { adapter.notifyDataSetChanged(); - ((ManagerActivityLollipop) context).saveNodesToDevice( - Collections.singletonList(node), - true, false, false, false); + onNodeTapped(context, node, ((ManagerActivityLollipop) context)::saveNodeByTap, (ManagerActivityLollipop) context, (ManagerActivityLollipop) context); } } diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/IncomingSharesFragmentLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/IncomingSharesFragmentLollipop.java index cf9511ac3b4..0e0b0a42aa3 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/IncomingSharesFragmentLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/IncomingSharesFragmentLollipop.java @@ -9,12 +9,10 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; import java.util.ArrayList; import java.util.List; -import mega.privacy.android.app.MegaApplication; import mega.privacy.android.app.R; import mega.privacy.android.app.components.NewGridRecyclerView; import mega.privacy.android.app.fragments.MegaNodeBaseFragment; @@ -22,14 +20,11 @@ import mega.privacy.android.app.lollipop.adapters.MegaNodeAdapter; import mega.privacy.android.app.utils.CloudStorageOptionControlUtil; import mega.privacy.android.app.utils.ColorUtils; -import mega.privacy.android.app.utils.MegaNodeUtil; -import nz.mega.sdk.MegaApiJava; import nz.mega.sdk.MegaNode; import static mega.privacy.android.app.lollipop.ManagerActivityLollipop.INCOMING_TAB; import static mega.privacy.android.app.utils.MegaNodeUtil.allHaveFullAccess; import static mega.privacy.android.app.utils.MegaNodeUtil.areAllFileNodes; -import static mega.privacy.android.app.utils.SortUtil.*; import static mega.privacy.android.app.utils.Constants.*; import static mega.privacy.android.app.utils.LogUtil.*; import static mega.privacy.android.app.utils.Util.*; @@ -123,6 +118,8 @@ public static IncomingSharesFragmentLollipop newInstance() { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + logDebug("Parent Handle: " + managerActivity.getParentHandleIncoming()); if (megaApi.getRootNode() == null) { @@ -137,14 +134,20 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa v = getListView(inflater, container); if (adapter == null) { - adapter = new MegaNodeAdapter(context, this, nodes, managerActivity.getParentHandleIncoming(), recyclerView, null, INCOMING_SHARES_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); + adapter = new MegaNodeAdapter(context, this, nodes, + managerActivity.getParentHandleIncoming(), recyclerView, + INCOMING_SHARES_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST, sortByHeaderViewModel); } } else { v = getGridView(inflater, container); if (adapter == null) { - adapter = new MegaNodeAdapter(context, this, nodes, managerActivity.getParentHandleIncoming(), recyclerView, null, INCOMING_SHARES_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_GRID); + adapter = new MegaNodeAdapter(context, this, nodes, + managerActivity.getParentHandleIncoming(), recyclerView, + INCOMING_SHARES_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_GRID, sortByHeaderViewModel); } + + gridLayoutManager.setSpanSizeLookup(adapter.getSpanSizeLookup(gridLayoutManager.getSpanCount())); } adapter.setParentHandle(managerActivity.getParentHandleIncoming()); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/OutgoingSharesFragmentLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/OutgoingSharesFragmentLollipop.java index a40672dc957..2ad7d314e66 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/OutgoingSharesFragmentLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/OutgoingSharesFragmentLollipop.java @@ -135,6 +135,8 @@ public static OutgoingSharesFragmentLollipop newInstance() { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + logDebug("onCreateView"); if (megaApi.getRootNode() == null) { @@ -149,14 +151,20 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa v = getListView(inflater, container); if (adapter == null) { - adapter = new MegaNodeAdapter(context, this, nodes, managerActivity.getParentHandleOutgoing(), recyclerView, null, OUTGOING_SHARES_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); + adapter = new MegaNodeAdapter(context, this, nodes, + managerActivity.getParentHandleOutgoing(), recyclerView, + OUTGOING_SHARES_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST, sortByHeaderViewModel); } } else { v = getGridView(inflater, container); if (adapter == null) { - adapter = new MegaNodeAdapter(context, this, nodes, managerActivity.getParentHandleOutgoing(), recyclerView, null, OUTGOING_SHARES_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_GRID); + adapter = new MegaNodeAdapter(context, this, nodes, + managerActivity.getParentHandleOutgoing(), recyclerView, + OUTGOING_SHARES_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_GRID, sortByHeaderViewModel); } + + gridLayoutManager.setSpanSizeLookup(adapter.getSpanSizeLookup(gridLayoutManager.getSpanCount())); } adapter.setParentHandle(managerActivity.getParentHandleOutgoing()); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/RubbishBinFragmentLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/RubbishBinFragmentLollipop.java index 41af28e4919..570bc091c0b 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/RubbishBinFragmentLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/RubbishBinFragmentLollipop.java @@ -16,6 +16,7 @@ import androidx.core.content.FileProvider; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.ActionMode; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -53,6 +54,7 @@ import javax.inject.Inject; import dagger.hilt.android.AndroidEntryPoint; +import kotlin.Unit; import mega.privacy.android.app.DatabaseHandler; import mega.privacy.android.app.MegaApplication; import mega.privacy.android.app.MegaPreferences; @@ -60,7 +62,9 @@ import mega.privacy.android.app.R; import mega.privacy.android.app.components.CustomizedGridLayoutManager; import mega.privacy.android.app.components.NewGridRecyclerView; -import mega.privacy.android.app.components.SimpleDividerItemDecoration; +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.globalmanagement.SortOrderManagement; import mega.privacy.android.app.lollipop.FullScreenImageViewerLollipop; import mega.privacy.android.app.lollipop.ManagerActivityLollipop; @@ -79,6 +83,7 @@ import static mega.privacy.android.app.utils.MegaApiUtils.*; import static mega.privacy.android.app.utils.MegaNodeUtil.manageTextFileIntent; import static mega.privacy.android.app.utils.MegaNodeUtil.manageURLNode; +import static mega.privacy.android.app.utils.MegaNodeUtil.onNodeTapped; import static mega.privacy.android.app.utils.Util.*; @AndroidEntryPoint @@ -230,6 +235,17 @@ public void selectAll(){ } } + /** + * Shows the Sort by panel. + * + * @param unit Unit event. + * @return Null. + */ + private Unit showSortByPanel(Unit unit) { + ((ManagerActivityLollipop) context).showNewSortByPanel(ORDER_CLOUD); + return null; + } + public static RubbishBinFragmentLollipop newInstance() { logDebug("newInstance"); RubbishBinFragmentLollipop fragment = new RubbishBinFragmentLollipop(); @@ -261,6 +277,12 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle sav return null; } + SortByHeaderViewModel sortByHeaderViewModel = new ViewModelProvider(this) + .get(SortByHeaderViewModel.class); + + sortByHeaderViewModel.getShowDialogEvent().observe(getViewLifecycleOwner(), + new EventObserver<>(this::showSortByPanel)); + display = ((Activity)context).getWindowManager().getDefaultDisplay(); outMetrics = new DisplayMetrics (); display.getMetrics(outMetrics); @@ -299,7 +321,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle sav recyclerView.setPadding(0, 0, 0, scaleHeightPx(85, outMetrics)); recyclerView.setClipToPadding(false); recyclerView.setItemAnimator(noChangeRecyclerViewItemAnimator()); - recyclerView.addItemDecoration(new SimpleDividerItemDecoration(requireContext())); + recyclerView.addItemDecoration(new PositionDividerItemDecoration(requireContext(), getResources().getDisplayMetrics())); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { @@ -313,7 +335,9 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { emptyTextViewFirst = (TextView) v.findViewById(R.id.rubbishbin_list_empty_text_first); if (adapter == null){ - adapter = new MegaNodeAdapter(context, this, nodes, ((ManagerActivityLollipop)context).getParentHandleRubbish(), recyclerView, null, RUBBISH_BIN_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); + adapter = new MegaNodeAdapter(context, this, nodes, + ((ManagerActivityLollipop)context).getParentHandleRubbish(), recyclerView, + RUBBISH_BIN_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST, sortByHeaderViewModel); } else{ adapter.setParentHandle(((ManagerActivityLollipop)context).getParentHandleRubbish()); @@ -416,7 +440,9 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { emptyTextViewFirst = (TextView) v.findViewById(R.id.rubbishbin_grid_empty_text_first); if (adapter == null){ - adapter = new MegaNodeAdapter(context, this, nodes, ((ManagerActivityLollipop)context).getParentHandleRubbish(), recyclerView, null, RUBBISH_BIN_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_GRID); + adapter = new MegaNodeAdapter(context, this, nodes, + ((ManagerActivityLollipop)context).getParentHandleRubbish(), recyclerView, + RUBBISH_BIN_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_GRID, sortByHeaderViewModel); } else{ adapter.setParentHandle(((ManagerActivityLollipop)context).getParentHandleRubbish()); @@ -425,6 +451,8 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { adapter.setAdapterType(MegaNodeAdapter.ITEM_VIEW_TYPE_GRID); } + gridLayoutManager.setSpanSizeLookup(adapter.getSpanSizeLookup(gridLayoutManager.getSpanCount())); + adapter.setMultipleSelect(false); recyclerView.setAdapter(adapter); @@ -801,9 +829,7 @@ else if (MimeTypeList.typeForName(nodes.get(position).getName()).isURL()) { manageTextFileIntent(requireContext(), nodes.get(position), RUBBISH_BIN_ADAPTER); } else { adapter.notifyDataSetChanged(); - ((ManagerActivityLollipop) context).saveNodesToDevice( - Collections.singletonList(nodes.get(position)), - true, false, false, false); + onNodeTapped(context, nodes.get(position), ((ManagerActivityLollipop) context)::saveNodeByTap, (ManagerActivityLollipop) context, (ManagerActivityLollipop) context); } } } diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/SearchFragmentLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/SearchFragmentLollipop.java index bc194db3645..c632be8e9c1 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/SearchFragmentLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/managerSections/SearchFragmentLollipop.java @@ -15,6 +15,7 @@ import androidx.core.content.FileProvider; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.view.ActionMode; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.DefaultItemAnimator; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -55,13 +56,16 @@ import dagger.hilt.android.AndroidEntryPoint; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.schedulers.Schedulers; +import kotlin.Unit; import mega.privacy.android.app.MegaApplication; 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.NewGridRecyclerView; -import mega.privacy.android.app.components.SimpleDividerItemDecoration; +import mega.privacy.android.app.components.PositionDividerItemDecoration; import mega.privacy.android.app.components.scrollBar.FastScroller; +import mega.privacy.android.app.fragments.homepage.EventObserver; +import mega.privacy.android.app.fragments.homepage.SortByHeaderViewModel; import mega.privacy.android.app.search.callback.SearchActionsCallback; import mega.privacy.android.app.search.usecase.SearchNodesUseCase; import mega.privacy.android.app.globalmanagement.SortOrderManagement; @@ -94,6 +98,7 @@ import static mega.privacy.android.app.utils.MegaNodeUtil.areAllFileNodes; import static mega.privacy.android.app.utils.MegaNodeUtil.manageTextFileIntent; import static mega.privacy.android.app.utils.MegaNodeUtil.manageURLNode; +import static mega.privacy.android.app.utils.MegaNodeUtil.onNodeTapped; import static mega.privacy.android.app.utils.Util.*; @AndroidEntryPoint @@ -487,11 +492,28 @@ public void checkScroll () { } } } + + /** + * Shows the Sort by panel. + * + * @param unit Unit event. + * @return Null. + */ + private Unit showSortByPanel(Unit unit) { + ((ManagerActivityLollipop) context).showNewSortByPanel(ORDER_CLOUD); + return null; + } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { logDebug("onCreateView"); + SortByHeaderViewModel sortByHeaderViewModel = new ViewModelProvider(this) + .get(SortByHeaderViewModel.class); + + sortByHeaderViewModel.getShowDialogEvent().observe(getViewLifecycleOwner(), + new EventObserver<>(this::showSortByPanel)); + if (megaApi == null){ megaApi = ((MegaApplication) ((Activity)context).getApplication()).getMegaApi(); } @@ -516,12 +538,11 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa recyclerView.setPadding(0, 0, 0, scaleHeightPx(85, outMetrics)); recyclerView.setClipToPadding(false); recyclerView.setClipToPadding(false); -// recyclerView.addItemDecoration(new SimpleDividerItemDecoration(context, outMetrics)); mLayoutManager = new LinearLayoutManager(context); recyclerView.setLayoutManager(mLayoutManager); recyclerView.setHasFixedSize(true); recyclerView.setItemAnimator(noChangeRecyclerViewItemAnimator()); - recyclerView.addItemDecoration(new SimpleDividerItemDecoration(requireContext())); + recyclerView.addItemDecoration(new PositionDividerItemDecoration(requireContext(), getOutMetrics())); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { @@ -535,7 +556,9 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { emptyTextViewFirst = (TextView) v.findViewById(R.id.file_list_empty_text_first); if (adapter == null){ - adapter = new MegaNodeAdapter(context, this, nodes, ((ManagerActivityLollipop)context).getParentHandleSearch(), recyclerView, null, SEARCH_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST); + adapter = new MegaNodeAdapter(context, this, nodes, + ((ManagerActivityLollipop)context).getParentHandleSearch(), recyclerView, + SEARCH_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_LIST, sortByHeaderViewModel); } else{ adapter.setListFragment(recyclerView); @@ -568,12 +591,16 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) { emptyTextViewFirst = (TextView) v.findViewById(R.id.file_grid_empty_text_first); if (adapter == null){ - adapter = new MegaNodeAdapter(context, this, nodes, ((ManagerActivityLollipop)context).getParentHandleSearch(), recyclerView, null, SEARCH_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_GRID); + adapter = new MegaNodeAdapter(context, this, nodes, + ((ManagerActivityLollipop)context).getParentHandleSearch(), recyclerView, + SEARCH_ADAPTER, MegaNodeAdapter.ITEM_VIEW_TYPE_GRID, sortByHeaderViewModel); } else{ adapter.setListFragment(recyclerView); adapter.setAdapterType(MegaNodeAdapter.ITEM_VIEW_TYPE_GRID); } + + gridLayoutManager.setSpanSizeLookup(adapter.getSpanSizeLookup(gridLayoutManager.getSpanCount())); } adapter.setMultipleSelect(false); @@ -932,9 +959,7 @@ else if (MimeTypeList.typeForName(nodes.get(position).getName()).isURL()) { manageTextFileIntent(requireContext(), nodes.get(position), SEARCH_ADAPTER); } else{ adapter.notifyDataSetChanged(); - ((ManagerActivityLollipop) context).saveNodesToDevice( - Collections.singletonList(nodes.get(position)), - true, false, false, false); + onNodeTapped(context, nodes.get(position), ((ManagerActivityLollipop) context)::saveNodeByTap, (ManagerActivityLollipop) context, (ManagerActivityLollipop) context); } } } diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/megachat/ChatActivityLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/megachat/ChatActivityLollipop.java index c3000a9065a..37437f93953 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/megachat/ChatActivityLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/megachat/ChatActivityLollipop.java @@ -84,8 +84,8 @@ import dagger.hilt.android.AndroidEntryPoint; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.schedulers.Schedulers; +import kotlin.Unit; import mega.privacy.android.app.BuildConfig; -import mega.privacy.android.app.DatabaseHandler; import mega.privacy.android.app.MegaApplication; import mega.privacy.android.app.MimeTypeList; import mega.privacy.android.app.R; @@ -2711,7 +2711,7 @@ public boolean onOptionsItemSelected(MenuItem item) { if(recordView.isRecordingNow()) break; if(participatingInACall()){ - showConfirmationInACall(this); + showConfirmationInACall(this, StringResourcesUtils.getString(R.string.ongoing_call_content), passcodeManagement); break; } @@ -2726,7 +2726,7 @@ public boolean onOptionsItemSelected(MenuItem item) { if(recordView.isRecordingNow()) break; if(CallUtil.participatingInACall()){ - showConfirmationInACall(this); + showConfirmationInACall(this, StringResourcesUtils.getString(R.string.ongoing_call_content), passcodeManagement); break; } @@ -2787,12 +2787,6 @@ public boolean onOptionsItemSelected(MenuItem item) { return super.onOptionsItemSelected(item); } - private void showMeetingOptionsPanel(){ - if (isBottomSheetDialogShown(bottomSheetDialogFragment)) return; - bottomSheetDialogFragment = new MeetingBottomSheetDialogFragment(); - bottomSheetDialogFragment.show(getSupportFragmentManager(), bottomSheetDialogFragment.getTag()); - } - /* *Prepare recording */ @@ -5387,8 +5381,7 @@ else if (MimeTypeList.typeForName(node.getName()).isVideoReproducible()||MimeTyp } else if (MimeTypeList.typeForName(node.getName()).isOpenableTextFile(node.getSize())) { manageTextFileIntent(this, m.getMessage().getMsgId(), idChat); } else { - logDebug("NOT Image, pdf, audio or video - show node attachment panel for one node"); - openWith(this, node); + onNodeTapped(this, node, this::saveNodeByTap, this, this); } } } @@ -6318,6 +6311,18 @@ public void sendToDownload(MegaNodeList nodelist){ nodeSaver.downloadVoiceClip(nodelist); } + /** + * Upon a node is tapped, if it cannot be previewed in-app, + * then download it first, this download will be marked as "download by tap". + * Since it's down + * + * @param node Node to be downloaded. + */ + public Unit saveNodeByTap(MegaNode node) { + nodeSaver.saveNodes(Collections.singletonList(node), true, false, false, true, false, true); + return null; + } + @Override public void onReactionUpdate(MegaChatApiJava api, long msgid, String reaction, int count) { MegaChatMessage message = api.getMessage(idChat, msgid); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/megachat/ChatExplorerActivity.java b/app/src/main/java/mega/privacy/android/app/lollipop/megachat/ChatExplorerActivity.java index 8a25d8573f2..f61e133b7eb 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/megachat/ChatExplorerActivity.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/megachat/ChatExplorerActivity.java @@ -166,8 +166,6 @@ public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu items for use in the action bar MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.file_explorer_action, menu); - menu.findItem(R.id.cab_menu_sort).setVisible(false); - menu.findItem(R.id.cab_menu_grid_list).setVisible(false); searchMenuItem = menu.findItem(R.id.cab_menu_search); createFolderMenuItem = menu.findItem(R.id.cab_menu_create_folder); newChatMenuItem = menu.findItem(R.id.cab_menu_new_chat); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/megachat/GroupChatInfoActivityLollipop.java b/app/src/main/java/mega/privacy/android/app/lollipop/megachat/GroupChatInfoActivityLollipop.java index 40ceed507eb..ed8b601d5da 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/megachat/GroupChatInfoActivityLollipop.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/megachat/GroupChatInfoActivityLollipop.java @@ -47,7 +47,7 @@ import mega.privacy.android.app.MegaApplication; import mega.privacy.android.app.R; -import mega.privacy.android.app.components.GroupParticipantsDividerItemDecoration; +import mega.privacy.android.app.components.PositionDividerItemDecoration; import mega.privacy.android.app.components.twemoji.EmojiEditText; import mega.privacy.android.app.interfaces.SnackbarShower; import mega.privacy.android.app.listeners.GetAttrUserListener; @@ -239,7 +239,7 @@ protected void onCreate(Bundle savedInstanceState) { aB.setTitle(getString(R.string.group_chat_info_label).toUpperCase()); recyclerView = findViewById(R.id.chat_group_contact_properties_list); - recyclerView.addItemDecoration(new GroupParticipantsDividerItemDecoration(this)); + recyclerView.addItemDecoration(new PositionDividerItemDecoration(this, getOutMetrics())); recyclerView.setHasFixedSize(true); linearLayoutManager = new LinearLayoutManager(this); recyclerView.setLayoutManager(linearLayoutManager); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/megachat/chatAdapters/MegaChatLollipopAdapter.java b/app/src/main/java/mega/privacy/android/app/lollipop/megachat/chatAdapters/MegaChatLollipopAdapter.java index ccf8c71a386..5b1a4b6c25f 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/megachat/chatAdapters/MegaChatLollipopAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/megachat/chatAdapters/MegaChatLollipopAdapter.java @@ -3459,8 +3459,9 @@ public void bindMegaLinkMessage(ViewHolderMessageChat holder, AndroidMegaChatMes if (message.isEdited()) { logDebug("Message is edited"); - Spannable content = new SpannableString(messageContent + " "); + Spannable content = new SpannableString(messageContent); content.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, R.color.grey_087_white_087)), 0, content.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + holder.urlContactMessageText.setText(content + " "); Spannable edited = new SpannableString(context.getString(R.string.edited_message_text)); edited.setSpan(new RelativeSizeSpan(0.70f), 0, edited.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); @@ -3474,7 +3475,6 @@ public void bindMegaLinkMessage(ViewHolderMessageChat holder, AndroidMegaChatMes } checkEmojiSize(messageContent, holder.urlContactMessageText); - holder.urlContactMessageText.setText(holder.urlContactMessageText.getText().toString()); holder.ownMessageLayout.setVisibility(View.GONE); holder.contactMessageLayout.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/mega/privacy/android/app/lollipop/megachat/chatAdapters/MegaParticipantsChatLollipopAdapter.java b/app/src/main/java/mega/privacy/android/app/lollipop/megachat/chatAdapters/MegaParticipantsChatLollipopAdapter.java index f0901fe1749..d59f5da36d2 100644 --- a/app/src/main/java/mega/privacy/android/app/lollipop/megachat/chatAdapters/MegaParticipantsChatLollipopAdapter.java +++ b/app/src/main/java/mega/privacy/android/app/lollipop/megachat/chatAdapters/MegaParticipantsChatLollipopAdapter.java @@ -4,9 +4,11 @@ import android.graphics.Bitmap; import androidx.appcompat.widget.SwitchCompat; +import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.RecyclerView; +import android.text.TextUtils; import android.util.DisplayMetrics; import android.view.Display; import android.view.LayoutInflater; @@ -92,7 +94,7 @@ public ViewHolderParticipants(View v) { super(v); } - RelativeLayout itemLayout; + ConstraintLayout itemLayout; } public static class ViewHolderParticipantsList extends ViewHolderParticipants { @@ -104,9 +106,8 @@ public ViewHolderParticipantsList(View v) { private ImageView verifiedIcon; private MarqueeTextView textViewContent; private EmojiTextView textViewContactName; - private RelativeLayout threeDotsLayout; + private ImageView textViewContactIcon; private ImageView imageButtonThreeDots; - private ImageView statusImage; private ImageView permissionsIcon; private int currentPosition; @@ -257,19 +258,9 @@ public ViewHolderParticipants onCreateViewHolder(ViewGroup parent, int viewType) holderList.verifiedIcon = v.findViewById(R.id.verified_icon); holderList.textViewContactName = v.findViewById(R.id.participant_list_name); holderList.textViewContent = v.findViewById(R.id.participant_list_content); - holderList.threeDotsLayout = v.findViewById(R.id.participant_list_three_dots_layout); + holderList.textViewContactIcon = v.findViewById(R.id.participant_list_icon_end); holderList.imageButtonThreeDots = v.findViewById(R.id.participant_list_three_dots); holderList.permissionsIcon = v.findViewById(R.id.participant_list_permissions); - holderList.statusImage = v.findViewById(R.id.group_participants_state_circle); - - if (isScreenInPortrait(groupChatInfoActivity)) { - holderList.textViewContactName.setMaxWidthEmojis(scaleWidthPx(MAX_WIDTH_PORT, outMetrics)); - holderList.textViewContent.setMaxWidth(scaleWidthPx(MAX_WIDTH_PORT, outMetrics)); - } else { - holderList.textViewContactName.setMaxWidthEmojis(scaleWidthPx(MAX_WIDTH_LAND, outMetrics)); - holderList.textViewContent.setMaxWidth(scaleWidthPx(MAX_WIDTH_LAND, outMetrics)); - } - holderList.itemLayout.setTag(holderList); v.setTag(holderList); @@ -441,22 +432,19 @@ public void onBindViewHolder(ViewHolderParticipants holder, int position) { holderParticipantsList.imageView.setImageBitmap(getDefaultAvatar(avatarColor, holderParticipantsList.fullName, AVATAR_SIZE, true)); } + holderParticipantsList.textViewContactName.setText(participant.getFullName()); MegaUser contact = participant.isEmpty() ? null : megaApi.getContact(participant.getEmail()); holderParticipantsList.verifiedIcon.setVisibility(contact != null && megaApi.areCredentialsVerified(contact) ? View.VISIBLE : View.GONE); - - int userStatus = handle == megaChatApi.getMyUserHandle() ? megaChatApi.getOnlineStatus() : getUserStatus(handle); - setContactStatus(userStatus, ((ViewHolderParticipantsList) holder).statusImage, ((ViewHolderParticipantsList) holder).textViewContent, StatusIconLocation.STANDARD); + setContactStatusParticipantList(userStatus, ((ViewHolderParticipantsList) holder).textViewContactIcon, ((ViewHolderParticipantsList) holder).textViewContent, StatusIconLocation.STANDARD); setContactLastGreen(groupChatInfoActivity, userStatus, participant.getLastGreen(), ((ViewHolderParticipantsList) holder).textViewContent); - holderParticipantsList.textViewContactName.setText(holderParticipantsList.fullName); - if (isPreview && megaChatApi.getInitState() == INIT_ANONYMOUS) { holderParticipantsList.imageButtonThreeDots.setColorFilter(ContextCompat.getColor(groupChatInfoActivity, R.color.grey_038_white_038)); - holderParticipantsList.threeDotsLayout.setOnClickListener(null); + holderParticipantsList.imageButtonThreeDots.setOnClickListener(null); holderParticipantsList.itemLayout.setOnClickListener(null); } else { - holderParticipantsList.threeDotsLayout.setOnClickListener(this); + holderParticipantsList.imageButtonThreeDots.setOnClickListener(this); holderParticipantsList.itemLayout.setOnClickListener(this); holderParticipantsList.imageButtonThreeDots.setColorFilter(ContextCompat.getColor(groupChatInfoActivity, R.color.grey_054_white_054)); } @@ -471,7 +459,7 @@ public void onBindViewHolder(ViewHolderParticipants holder, int position) { holderParticipantsList.permissionsIcon.setImageResource(R.drawable.ic_permissions_read_only); } - holderParticipantsList.threeDotsLayout.setTag(holder); + holderParticipantsList.imageButtonThreeDots.setTag(holder); break; case ITEM_VIEW_TYPE_ADD_PARTICIPANT: @@ -592,7 +580,7 @@ public void onClick(View v) { } switch (v.getId()) { - case R.id.participant_list_three_dots_layout: + case R.id.participant_list_three_dots: case R.id.participant_list_item_layout: ViewHolderParticipantsList holder = (ViewHolderParticipantsList) v.getTag(); int currentPosition = holder.currentPosition; @@ -748,7 +736,7 @@ private Bitmap checkParticipant(ViewHolderParticipantsList holderParticipantsLis if (isTextEmpty(nameFileEmail)) { holderParticipantsList.imageButtonThreeDots.setColorFilter(ContextCompat.getColor(groupChatInfoActivity, R.color.grey_038_white_038)); - holderParticipantsList.threeDotsLayout.setOnClickListener(null); + holderParticipantsList.imageButtonThreeDots.setOnClickListener(null); holderParticipantsList.itemLayout.setOnClickListener(null); avatarBitmap = getAvatarBitmap(nameFileHandle); } else { diff --git a/app/src/main/java/mega/privacy/android/app/mediaplayer/service/AudioPlayerService.kt b/app/src/main/java/mega/privacy/android/app/mediaplayer/service/AudioPlayerService.kt index b0d7c23b4e7..fdfbba5ebd7 100644 --- a/app/src/main/java/mega/privacy/android/app/mediaplayer/service/AudioPlayerService.kt +++ b/app/src/main/java/mega/privacy/android/app/mediaplayer/service/AudioPlayerService.kt @@ -1,8 +1,16 @@ package mega.privacy.android.app.mediaplayer.service +import android.content.Intent + /** * Extending MediaPlayerService is to support two running instances at the same time, * video player and audio player, so that video player could "interrupt" audio player, * and resume audio player when video player is stopped. */ -class AudioPlayerService : MediaPlayerService() +class AudioPlayerService : MediaPlayerService() { + override fun onTaskRemoved(rootIntent: Intent?) { + super.onTaskRemoved(rootIntent) + //Stop audio player when app is killed. + stopAudioPlayer() + } +} 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 c799ae27f50..b71f89f61dc 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 @@ -33,6 +33,7 @@ class MeetingActivity : BaseActivity() { const val MEETING_ACTION_CREATE = "create_meeting" const val MEETING_ACTION_GUEST = "join_meeting_as_guest" const val MEETING_ACTION_IN = "in_meeting" + const val MEETING_ACTION_MAKE_MODERATOR = "make_moderator" const val MEETING_ACTION_RINGING = "ringing_meeting" const val MEETING_ACTION_RINGING_VIDEO_ON = "ringing_meeting_video_on" const val MEETING_ACTION_RINGING_VIDEO_OFF = "ringing_meeting_video_off" @@ -50,6 +51,7 @@ class MeetingActivity : BaseActivity() { @Inject lateinit var passcodeUtil: PasscodeUtil + @Inject lateinit var passcodeManagement: PasscodeManagement @@ -213,6 +215,7 @@ class MeetingActivity : BaseActivity() { MEETING_ACTION_GUEST -> R.id.joinMeetingAsGuestFragment MEETING_ACTION_START, MEETING_ACTION_IN -> R.id.inMeetingFragment MEETING_ACTION_RINGING -> R.id.ringingMeetingFragment + MEETING_ACTION_MAKE_MODERATOR -> R.id.makeModeratorFragment else -> R.id.createMeetingFragment } @@ -289,14 +292,17 @@ class MeetingActivity : BaseActivity() { } is InMeetingFragment -> { // Prevent guest from quitting the call by pressing back - if(!isGuest){ + if (!isGuest) { currentFragment.removeUI() sendQuitCallEvent() } } + is MakeModeratorFragment -> { + currentFragment.cancel() + } } - if (currentFragment !is InMeetingFragment || !isGuest) { + if (currentFragment !is MakeModeratorFragment && (currentFragment !is InMeetingFragment || !isGuest)) { finish() } } diff --git a/app/src/main/java/mega/privacy/android/app/meeting/activity/MeetingActivityRepository.kt b/app/src/main/java/mega/privacy/android/app/meeting/activity/MeetingActivityRepository.kt index 20177cc7923..400461bdb43 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/activity/MeetingActivityRepository.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/activity/MeetingActivityRepository.kt @@ -9,11 +9,11 @@ import kotlinx.coroutines.withContext import mega.privacy.android.app.MegaApplication import mega.privacy.android.app.di.MegaApi import mega.privacy.android.app.listeners.BaseListener +import mega.privacy.android.app.lollipop.controllers.ChatController import mega.privacy.android.app.lollipop.megachat.AppRTCAudioManager import mega.privacy.android.app.meeting.listeners.IndividualCallVideoListener -import mega.privacy.android.app.utils.AvatarUtil -import mega.privacy.android.app.utils.CacheFolderManager -import mega.privacy.android.app.utils.Constants +import mega.privacy.android.app.meeting.listeners.MeetingAvatarListener +import mega.privacy.android.app.utils.* import mega.privacy.android.app.utils.FileUtil.JPG_EXTENSION import mega.privacy.android.app.utils.LogUtil.logDebug import nz.mega.sdk.* @@ -202,4 +202,62 @@ class MeetingActivityRepository @Inject constructor( else -> megaChatApi.getChatRoom(chatId) } } + + /** + * Method for getting a participant's avatar + * + * @param peerId user handle of participant + */ + fun getAvatarBitmapByPeerId(peerId: Long): Bitmap? { + val mail = ChatController(context).getParticipantEmail(peerId) + val userHandleString = MegaApiAndroid.userHandleToBase64(peerId) + val myUserHandleEncoded = MegaApiAndroid.userHandleToBase64(megaApi.myUserHandleBinary) + var bitmap = when { + userHandleString == myUserHandleEncoded -> { + AvatarUtil.getAvatarBitmap(mail) + } + TextUtil.isTextEmpty(mail) -> AvatarUtil.getAvatarBitmap(userHandleString) + else -> AvatarUtil.getUserAvatar( + userHandleString, + mail + ) + } + + if (bitmap == null) { + megaApi.getUserAvatar( + mail, + CacheFolderManager.buildAvatarFile( + context, + mail + JPG_EXTENSION + ).absolutePath, MeetingAvatarListener(context, peerId) + ) + bitmap = CallUtil.getDefaultAvatarCall( + MegaApplication.getInstance().applicationContext, + peerId + ) + } + + return bitmap + } + + /** + * Give moderator permissions to a call participant. + * + * @param chatId Chat ID of the call + * @param userHandle user handle of a participant + * @param listener MegaChatRequestListenerInterface + */ + fun giveModeratorPermissions( + chatId: Long, + userHandle: Long, + listener: MegaChatRequestListenerInterface? + ) { + megaChatApi.updateChatPermissions( + chatId, + userHandle, + MegaChatRoom.PRIV_MODERATOR, + listener + ) + } + } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/meeting/activity/MeetingActivityViewModel.kt b/app/src/main/java/mega/privacy/android/app/meeting/activity/MeetingActivityViewModel.kt index 7e3a8fdda10..eedddb4245c 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/activity/MeetingActivityViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/activity/MeetingActivityViewModel.kt @@ -104,7 +104,7 @@ class MeetingActivityViewModel @ViewModelInject constructor( val meetingLinkLiveData: LiveData = _meetingLinkLiveData // Show snack bar - private val _snackBarLiveData: MutableLiveData = MutableLiveData() + private val _snackBarLiveData = MutableLiveData("") val snackBarLiveData: LiveData = _snackBarLiveData private val audioOutputStateObserver = @@ -545,4 +545,36 @@ class MeetingActivityViewModel @ViewModelInject constructor( fun showSnackBar(content: String) { _snackBarLiveData.value = content } + + /** + * Hide snack bar + */ + fun hideSnackBar(){ + _snackBarLiveData.value = "" + } + + /** + * Method for obtaining the bitmap of a participant's avatar + * + * @param peerId User handle of a participant + * @return The bitmap of a participant's avatar + */ + fun getAvatarBitmapByPeerId(peerId: Long): Bitmap? { + return meetingActivityRepository.getAvatarBitmapByPeerId(peerId) + } + + /** + * Give moderator permissions to a call participant. + * + * @param userHandle User handle of a participant + * @param listener MegaChatRequestListenerInterface + */ + fun giveModeratorPermissions( + userHandle: Long, + listener: MegaChatRequestListenerInterface? = null + ) { + currentChatId.value?.let { + meetingActivityRepository.giveModeratorPermissions(it, userHandle, listener) + } + } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/meeting/adapter/AssignParticipantViewHolders.kt b/app/src/main/java/mega/privacy/android/app/meeting/adapter/AssignParticipantViewHolders.kt index cc488098387..273c91ff852 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/adapter/AssignParticipantViewHolders.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/adapter/AssignParticipantViewHolders.kt @@ -1,40 +1,73 @@ package mega.privacy.android.app.meeting.adapter +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import mega.privacy.android.app.R -import mega.privacy.android.app.databinding.ItemAssignModeratorBinding +import mega.privacy.android.app.databinding.ItemParticipantChatListBinding import mega.privacy.android.app.databinding.ItemSelectedParticipantBinding -import mega.privacy.android.app.meeting.fragments.InMeetingViewModel +import mega.privacy.android.app.meeting.activity.MeetingActivityViewModel +import mega.privacy.android.app.MegaApplication +import mega.privacy.android.app.utils.Util +import org.jetbrains.anko.displayMetrics +/** + * The view holder for the participant view on the Make moderator screen. + * + * @param sharedModel MeetingActivityViewModel, the activity view model related to meetings + * @param select Callback to be called when participant item is clicked + * @param binding ItemParticipantChatListBinding + */ class AssignParticipantViewHolder( - private val viewModel: InMeetingViewModel, + private val sharedModel: MeetingActivityViewModel, private val select: ((Int) -> Unit), - private val binding: ItemAssignModeratorBinding + private val binding: ItemParticipantChatListBinding ) : RecyclerView.ViewHolder(binding.root) { + fun bind(participant: Participant) { - binding.name.text = participant.name + binding.participantListName.text = participant.name + + binding.participantListNameRl.setPadding( + 0, + 0, + Util.scaleWidthPx(16, MegaApplication.getInstance().displayMetrics), + 0 + ) if (participant.isChosenForAssign) { - binding.avatar.setImageResource(R.drawable.ic_select_folder) + binding.participantListThumbnail.setImageResource(R.drawable.ic_select_folder) } else { // Set actual avatar - binding.avatar.setImageBitmap(viewModel.getAvatarBitmapByPeerId(participant.peerId)) + binding.participantListThumbnail.setImageBitmap( + sharedModel.getAvatarBitmapByPeerId( + participant.peerId + ) + ) } - binding.assignLayout.setOnClickListener { + binding.participantListItemLayout.setOnClickListener { select.invoke(bindingAdapterPosition) } + + binding.verifiedIcon.isVisible = false + binding.participantListThreeDots.isVisible = false + binding.participantListPermissions.isVisible = false + binding.participantListAudio.isVisible = false + binding.participantListVideo.isVisible = false + binding.participantListContent.isVisible = false } } +/* + * This class is in charge of managing the participants that are selected to change their permissions to MODERATOR. + */ class SelectedParticipantViewHolder( - private val viewModel: InMeetingViewModel, + private val sharedModel: MeetingActivityViewModel, private val delete: (Participant) -> Unit, private val binding: ItemSelectedParticipantBinding ) : RecyclerView.ViewHolder(binding.root) { fun bind(participant: Participant) { binding.nameChip.text = participant.name - binding.avatar.setImageBitmap(viewModel.getAvatarBitmapByPeerId(participant.peerId)) + binding.avatar.setImageBitmap(sharedModel.getAvatarBitmapByPeerId(participant.peerId)) binding.itemLayoutChip.setOnClickListener { delete.invoke(participant) } diff --git a/app/src/main/java/mega/privacy/android/app/meeting/adapter/AssignParticipantsAdapter.kt b/app/src/main/java/mega/privacy/android/app/meeting/adapter/AssignParticipantsAdapter.kt index 3994cd23810..ceddebc3e64 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/adapter/AssignParticipantsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/adapter/AssignParticipantsAdapter.kt @@ -3,11 +3,17 @@ package mega.privacy.android.app.meeting.adapter import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.ListAdapter -import mega.privacy.android.app.databinding.ItemAssignModeratorBinding -import mega.privacy.android.app.meeting.fragments.InMeetingViewModel +import mega.privacy.android.app.databinding.ItemParticipantChatListBinding +import mega.privacy.android.app.meeting.activity.MeetingActivityViewModel +/** + * RecyclerView's ListAdapter to show participants list. + * + * @property sharedModel MeetingActivityViewModel, the activity view model related to meetings + * @property select Callback to be called when participant item is clicked + */ class AssignParticipantsAdapter( - private val inMeetingViewModel: InMeetingViewModel, + private val sharedModel: MeetingActivityViewModel, private val select: ((Int) -> Unit) ) : ListAdapter( AssignParticipantDiffCallback() @@ -19,8 +25,8 @@ class AssignParticipantsAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AssignParticipantViewHolder { val inflater = LayoutInflater.from(parent.context) return AssignParticipantViewHolder( - inMeetingViewModel, select, - ItemAssignModeratorBinding.inflate( + sharedModel, select, + ItemParticipantChatListBinding.inflate( inflater, parent, false diff --git a/app/src/main/java/mega/privacy/android/app/meeting/adapter/Participant.kt b/app/src/main/java/mega/privacy/android/app/meeting/adapter/Participant.kt index ed28f94f3c4..4111543234f 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/adapter/Participant.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/adapter/Participant.kt @@ -42,7 +42,7 @@ data class Participant( try { displayName = displayName.replace( "[A]", "" + + getColorHexString(context, R.color.grey_200) + "'>" ) displayName = displayName.replace("[/A]", "") } catch (e: Exception) { diff --git a/app/src/main/java/mega/privacy/android/app/meeting/adapter/ParticipantViewHolder.kt b/app/src/main/java/mega/privacy/android/app/meeting/adapter/ParticipantViewHolder.kt index 1a77c331608..210f8a64de2 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/adapter/ParticipantViewHolder.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/adapter/ParticipantViewHolder.kt @@ -2,65 +2,79 @@ package mega.privacy.android.app.meeting.adapter import android.graphics.PorterDuff import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import mega.privacy.android.app.R -import mega.privacy.android.app.databinding.ItemMeetingParticipantBinding +import mega.privacy.android.app.databinding.ItemParticipantChatListBinding /** * When use DataBinding here, when user fling the RecyclerView, the bottom sheet will have * extra top offset. Not use DataBinding could avoid this bug. */ class ParticipantViewHolder( - private val binding: ItemMeetingParticipantBinding, + private val binding: ItemParticipantChatListBinding, private val onParticipantOption: (Int) -> Unit ) : RecyclerView.ViewHolder(binding.root) { init { - binding.threeDots.setOnClickListener { + binding.participantListThreeDots.setOnClickListener { onParticipantOption(bindingAdapterPosition) } + + binding.verifiedIcon.isVisible = false + binding.participantListPermissions.isVisible = false + binding.participantListContent.isVisible = false + binding.callOptions.isVisible = true } fun bind(participant: Participant) { - binding.avatar.setImageBitmap(participant.avatar) - binding.name.text = participant.name - - if (participant.isModerator) { - val drawable = ContextCompat.getDrawable(binding.name.context, R.drawable.ic_moderator) - drawable?.setTint(ContextCompat.getColor(binding.name.context, R.color.teal_300_teal_200)) + binding.participantListThumbnail.setImageBitmap(participant.avatar) + binding.participantListName.text = + participant.getDisplayName(binding.participantListName.context) - binding.name.setCompoundDrawablesWithIntrinsicBounds( - null, - null, - drawable, - null - ) + if (!participant.isModerator) { + binding.participantListIconEnd.isVisible = false } else { - binding.name.setCompoundDrawablesWithIntrinsicBounds(null, null, null, null) + val drawable = ContextCompat.getDrawable( + binding.participantListName.context, + R.drawable.ic_moderator + ) + drawable?.setTint( + ContextCompat.getColor( + binding.participantListName.context, + R.color.teal_300_teal_200 + ) + ) + binding.participantListIconEnd.isVisible = true } - binding.name.text = participant.getDisplayName(binding.name.context) - if (participant.isAudioOn) { - binding.audioStatus.setImageResource(R.drawable.ic_mic_on) - binding.audioStatus.setColorFilter( - ContextCompat.getColor(binding.audioStatus.context, R.color.grey_054_white_054), + binding.participantListAudio.setImageResource(R.drawable.ic_mic_on) + binding.participantListAudio.setColorFilter( + ContextCompat.getColor( + binding.participantListAudio.context, + R.color.grey_054_white_054 + ), PorterDuff.Mode.SRC_IN ) } else { - binding.audioStatus.setImageResource(R.drawable.ic_mic_off_grey_red) - binding.audioStatus.colorFilter = null + binding.participantListAudio.setImageResource(R.drawable.ic_mic_off_grey_red) + binding.participantListAudio.colorFilter = null } if (participant.isVideoOn) { - binding.videoStatus.setImageResource(R.drawable.ic_video) - binding.videoStatus.setColorFilter( - ContextCompat.getColor(binding.videoStatus.context, R.color.grey_054_white_054), + binding.participantListVideo.setImageResource(R.drawable.ic_video) + binding.participantListVideo.setColorFilter( + ContextCompat.getColor( + binding.participantListVideo.context, + R.color.grey_054_white_054 + ), PorterDuff.Mode.SRC_IN ) + } else { - binding.videoStatus.setImageResource(R.drawable.ic_video_off_grey_red) - binding.videoStatus.colorFilter = null + binding.participantListVideo.setImageResource(R.drawable.ic_video_off_grey_red) + binding.participantListVideo.colorFilter = null } } } diff --git a/app/src/main/java/mega/privacy/android/app/meeting/adapter/ParticipantsAdapter.kt b/app/src/main/java/mega/privacy/android/app/meeting/adapter/ParticipantsAdapter.kt index eb351b834d6..12a051210d6 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/adapter/ParticipantsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/adapter/ParticipantsAdapter.kt @@ -4,7 +4,7 @@ import android.graphics.Bitmap import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.ListAdapter -import mega.privacy.android.app.databinding.ItemMeetingParticipantBinding +import mega.privacy.android.app.databinding.ItemParticipantChatListBinding import mega.privacy.android.app.meeting.listeners.BottomFloatingPanelListener import mega.privacy.android.app.utils.Constants.AVATAR_CHANGE import mega.privacy.android.app.utils.Constants.NAME_CHANGE @@ -20,7 +20,7 @@ class ParticipantsAdapter( override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ParticipantViewHolder { val inflater = LayoutInflater.from(parent.context) return ParticipantViewHolder( - ItemMeetingParticipantBinding.inflate(inflater, parent, false) + ItemParticipantChatListBinding.inflate(inflater, parent, false) ) { listener.onParticipantOption(getItem(it)) } diff --git a/app/src/main/java/mega/privacy/android/app/meeting/adapter/SelectedParticipantsAdapter.kt b/app/src/main/java/mega/privacy/android/app/meeting/adapter/SelectedParticipantsAdapter.kt index 557bf08c6d8..1290652a4c5 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/adapter/SelectedParticipantsAdapter.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/adapter/SelectedParticipantsAdapter.kt @@ -4,10 +4,10 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.ListAdapter import mega.privacy.android.app.databinding.ItemSelectedParticipantBinding -import mega.privacy.android.app.meeting.fragments.InMeetingViewModel +import mega.privacy.android.app.meeting.activity.MeetingActivityViewModel class SelectedParticipantsAdapter( - private val inMeetingViewModel: InMeetingViewModel, + private val sharedModel: MeetingActivityViewModel, private val delete: ((Participant) -> Unit) ) : ListAdapter( AssignParticipantDiffCallback() @@ -22,7 +22,7 @@ class SelectedParticipantsAdapter( ): SelectedParticipantViewHolder { val inflater = LayoutInflater.from(parent.context) return SelectedParticipantViewHolder( - inMeetingViewModel, delete, + sharedModel, delete, ItemSelectedParticipantBinding.inflate( inflater, parent, diff --git a/app/src/main/java/mega/privacy/android/app/meeting/fragments/AssignModeratorBottomFragment.kt b/app/src/main/java/mega/privacy/android/app/meeting/fragments/AssignModeratorBottomFragment.kt deleted file mode 100644 index 782392b0e4a..00000000000 --- a/app/src/main/java/mega/privacy/android/app/meeting/fragments/AssignModeratorBottomFragment.kt +++ /dev/null @@ -1,214 +0,0 @@ -package mega.privacy.android.app.meeting.fragments - -import android.annotation.SuppressLint -import android.app.Dialog -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isVisible -import androidx.recyclerview.widget.LinearLayoutManager -import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.bottomsheet.BottomSheetDialogFragment -import mega.privacy.android.app.R -import mega.privacy.android.app.components.SimpleDividerItemDecoration -import mega.privacy.android.app.databinding.ActivityAssignModeratorBinding -import mega.privacy.android.app.meeting.adapter.AssignParticipantsAdapter -import mega.privacy.android.app.meeting.adapter.Participant -import mega.privacy.android.app.meeting.adapter.SelectedParticipantsAdapter -import mega.privacy.android.app.utils.StringResourcesUtils - -/** - * AssignModerator page allow moderator assign other users moderator when they are leaving the meeting - */ -class AssignModeratorBottomFragment( - private val leaveMeeting: () -> Unit -) : BottomSheetDialogFragment() { - private lateinit var binding: ActivityAssignModeratorBinding - - private var selectedParticipants: MutableList = mutableListOf() - private var participants: MutableList = mutableListOf() - - private lateinit var participantsAdapter: AssignParticipantsAdapter - private lateinit var selectedParticipantsAdapter: SelectedParticipantsAdapter - private val inMeetingViewModel by lazy { (parentFragment as InMeetingFragment).inMeetingViewModel } - - @SuppressLint("RestrictedApi") - override fun setupDialog(dialog: Dialog, style: Int) { - super.setupDialog(dialog, style) - - binding = ActivityAssignModeratorBinding.inflate(layoutInflater) - initRecyclerview() - binding.btCancel.setOnClickListener { cancel() } - binding.btOk.setOnClickListener { makeModerators() } - binding.toolbar.setNavigationOnClickListener { dismiss() } - - inMeetingViewModel.participants.observe(this) { newData -> - update(newData.filter { inMeetingViewModel.isStandardUser(it.peerId) && !it.isGuest } - .map { it.copy() } - .toMutableList()) - } - - dialog.setContentView(binding.root) - } - - override fun onStart() { - super.onStart() - val dialog = dialog ?: return - - val bottomSheet: View = dialog.findViewById(R.id.design_bottom_sheet) - bottomSheet.layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT - - BottomSheetBehavior.from(bottomSheet).apply { - state = BottomSheetBehavior.STATE_EXPANDED - isFitToContents = false - skipCollapsed = true - isHideable = true - addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { - override fun onStateChanged(bottomSheet: View, newState: Int) { - if (newState == BottomSheetBehavior.STATE_DRAGGING) { - state = BottomSheetBehavior.STATE_EXPANDED - } - } - - override fun onSlide(bottomSheet: View, slideOffset: Float) {} - }) - - } - } - - private fun initRecyclerview() { - participantsAdapter = AssignParticipantsAdapter(inMeetingViewModel, selectCallback) - selectedParticipantsAdapter = - SelectedParticipantsAdapter(inMeetingViewModel, deleteCallback) - - binding.participantList.apply { - layoutManager = LinearLayoutManager(context) - itemAnimator = null - adapter = participantsAdapter - addItemDecoration(SimpleDividerItemDecoration(context)) - } - - binding.selectedParticipantList.apply { - layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) - itemAnimator = null - clipToPadding = false - adapter = selectedParticipantsAdapter - } - - participantsAdapter.submitList(participants.toList()) - } - - /** - * The call back function when users select participants, will update the selected list and participant list - */ - private val selectCallback = fun(position: Int) { - updateParticipantList(position) - - val participant = participants[position] - - if (participant.isChosenForAssign) { - selectedParticipants.add(participant) - } else { - selectedParticipants.remove(participant) - } - - updateSelectedParticipant() - } - - /** - * Update the selected participant list when user choose or delete the participant - */ - private fun updateSelectedParticipant() { - if (selectedParticipants.size > 0) { - binding.toolbar.subtitle = - StringResourcesUtils.getString(R.string.selected_items, selectedParticipants.size) - binding.moderatorAddsContainer.isVisible = true - binding.btOk.isEnabled = true - } else { - binding.moderatorAddsContainer.isVisible = false - binding.toolbar.subtitle = - StringResourcesUtils.getString(R.string.pick_new_moderator_message) - binding.btOk.isEnabled = false - } - - binding.moderatorAddsContainer.isVisible = selectedParticipants.size > 0 - - selectedParticipantsAdapter.submitList(selectedParticipants.toMutableList()) - } - - /** - * The call back function when users delete participants, will update the selected list and participant list - */ - private val deleteCallback = fun(participant: Participant) { - val position = participants.indexOf(participant) - - selectedParticipants.remove(participant) - updateSelectedParticipant() - updateParticipantList(position) - } - - /** - * Update the participant list when user choose or delete the participant - */ - private fun updateParticipantList(position: Int) { - val participant = participants[position] - participant.isChosenForAssign = !participant.isChosenForAssign - participants[position] = participant - - participantsAdapter.notifyItemChanged(position) - } - - /** - * Update the participant list and selected list when someone leave this meeting - * - * @param data new participant list - */ - fun update(data: MutableList) { - // Get the current selected id - participants = data - val oldSelect = selectedParticipants.map { it.peerId } - participants.forEach { - if (oldSelect.contains(it.peerId)) { - it.isChosenForAssign = true - } - } - - participantsAdapter.submitList(participants.toMutableList()) - - val newSelect = participants.filter { it.isChosenForAssign }.map { it.peerId } - selectedParticipants.run { - removeIf { - !newSelect.contains(it.peerId) - } - forEach { - it.isChosenForAssign = true - } - } - - selectedParticipantsAdapter.submitList(selectedParticipants.toMutableList()) - } - - /** - * Make selected participants to moderator - */ - private fun makeModerators() { - // Get the list and assign the user in the list to moderator - selectedParticipants.forEach { - inMeetingViewModel.updateChatPermissions(it.peerId) - } - - leaveMeeting.invoke() - dismiss() - } - - /** - * Cancel this action and close this page - */ - fun cancel() { - dismiss() - } - - companion object { - fun newInstance(leaveMeeting: () -> Unit) = AssignModeratorBottomFragment(leaveMeeting) - } - -} \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/meeting/fragments/BottomFloatingPanelViewHolder.kt b/app/src/main/java/mega/privacy/android/app/meeting/fragments/BottomFloatingPanelViewHolder.kt index 03ac3f5f203..3541424058a 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/fragments/BottomFloatingPanelViewHolder.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/fragments/BottomFloatingPanelViewHolder.kt @@ -7,7 +7,7 @@ import android.graphics.drawable.Drawable import android.graphics.drawable.GradientDrawable import android.graphics.drawable.InsetDrawable import android.graphics.drawable.LayerDrawable -import android.util.Log +import android.util.DisplayMetrics import android.view.Gravity import android.view.LayoutInflater import android.view.View @@ -18,7 +18,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.material.bottomsheet.BottomSheetBehavior import mega.privacy.android.app.R import mega.privacy.android.app.components.OnOffFab -import mega.privacy.android.app.components.SimpleDividerItemDecoration +import mega.privacy.android.app.components.PositionDividerItemDecoration import mega.privacy.android.app.databinding.InMeetingFragmentBinding import mega.privacy.android.app.lollipop.megachat.AppRTCAudioManager import mega.privacy.android.app.meeting.LockableBottomSheetBehavior @@ -42,6 +42,7 @@ class BottomFloatingPanelViewHolder( private val inMeetingViewModel: InMeetingViewModel, private val binding: InMeetingFragmentBinding, private val listener: BottomFloatingPanelListener, + private val displayMetrics: DisplayMetrics ) { private val context = binding.root.context private val floatingPanelView = binding.bottomFloatingPanel @@ -54,6 +55,7 @@ class BottomFloatingPanelViewHolder( private var collapsedTop = 0 private var popWindow: PopupWindow? = null + private lateinit var itemDecoration: PositionDividerItemDecoration /** * Save the Mic & Cam state, for revering state when hold state changed @@ -281,6 +283,8 @@ class BottomFloatingPanelViewHolder( bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { + listener.onChangePanelState() + bottomFloatingPanelExpanded = newState == BottomSheetBehavior.STATE_EXPANDED if (newState == BottomSheetBehavior.STATE_DRAGGING && inMeetingViewModel.isOneToOneCall()) { bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED @@ -399,12 +403,16 @@ class BottomFloatingPanelViewHolder( * Init recyclerview */ private fun setupRecyclerView() { + itemDecoration = PositionDividerItemDecoration(context, displayMetrics) + itemDecoration.setDrawAllDividers(true) + floatingPanelView.participants.apply { layoutManager = LinearLayoutManager(context) - itemAnimator = null + itemAnimator = Util.noChangeRecyclerViewItemAnimator() clipToPadding = false + setHasFixedSize(true) adapter = participantsAdapter - addItemDecoration(SimpleDividerItemDecoration(context)) + addItemDecoration(itemDecoration) } } diff --git a/app/src/main/java/mega/privacy/android/app/meeting/fragments/InMeetingFragment.kt b/app/src/main/java/mega/privacy/android/app/meeting/fragments/InMeetingFragment.kt index 21a36c81b68..26a141c7e92 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/fragments/InMeetingFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/fragments/InMeetingFragment.kt @@ -5,6 +5,7 @@ import android.app.Dialog import android.content.Intent import android.content.pm.ActivityInfo import android.content.res.Configuration +import android.graphics.Color import android.os.Build import android.os.Bundle import android.util.DisplayMetrics @@ -17,8 +18,9 @@ import androidx.annotation.RequiresApi import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels +import androidx.fragment.app.activityViewModels import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.google.android.material.appbar.MaterialToolbar import com.google.android.material.bottomsheet.BottomSheetBehavior @@ -57,7 +59,6 @@ import mega.privacy.android.app.listeners.ChatChangeVideoStreamListener import mega.privacy.android.app.listeners.SimpleChatRequestListener import mega.privacy.android.app.listeners.SimpleMegaRequestListener import mega.privacy.android.app.lollipop.AddContactActivityLollipop -import mega.privacy.android.app.lollipop.controllers.AccountController import mega.privacy.android.app.lollipop.megachat.AppRTCAudioManager import mega.privacy.android.app.mediaplayer.service.MediaPlayerService.Companion.pauseAudioPlayer import mega.privacy.android.app.mediaplayer.service.MediaPlayerService.Companion.resumeAudioPlayerIfNotInCall @@ -140,6 +141,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn private var meetingLink: String = "" private var isManualModeView = false private var isWaitingForAnswerCall = false + private var isWaitingForMakeModerator = false // Children fragments private var individualCallFragment: IndividualCallFragment? = null @@ -147,8 +149,6 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn private var gridViewCallFragment: GridViewCallFragment? = null private var speakerViewCallFragment: SpeakerViewCallFragment? = null - private var status = NOT_TYPE - // For internal UI/UX use private var previousY = -1f private var lastTouch: Long = 0 @@ -169,7 +169,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn // Meeting failed Dialog private var failedDialog: Dialog? = null - val inMeetingViewModel by viewModels() + val inMeetingViewModel: InMeetingViewModel by activityViewModels() private val enableOrDisableLocalVideoObserver = Observer { shouldBeEnabled -> val chatId = inMeetingViewModel.getChatId() @@ -261,7 +261,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn updatePanelAndToolbar(it) when (it.status) { - MegaChatCall.CALL_STATUS_INITIAL-> { + MegaChatCall.CALL_STATUS_INITIAL -> { bottomFloatingPanelViewHolder.disableEnableButtons( false, inMeetingViewModel.isCallOnHold() @@ -272,7 +272,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn if (inMeetingViewModel.amIAGuest()) { disableCamera() removeUI() - finishActivityAsGuest() + inMeetingViewModel.finishActivityAsGuest(meetingActivity) } else { finishActivity() } @@ -403,8 +403,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn logDebug("Session in progress, clientID = ${callAndSession.second.clientid}") val position = inMeetingViewModel.addParticipant( - callAndSession.second, - status + callAndSession.second ) position?.let { if (position != INVALID_POSITION) { @@ -588,13 +587,17 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn private val incompatibilitySnackbarObserver = Observer { // calculate the position of floating window related to snack bar bSnackbarShown = it - if(bSnackbarShown) { + if (bSnackbarShown) { meetingActivity.snackbar.view.post { snackbarTop = meetingActivity.snackbar.view.top snackbarBottom = meetingActivity.snackbar.view.bottom - adjustPositionOfFloatingWindow(bConsiderSnackbar = true, bTop = false, bBottom = true) + adjustPositionOfFloatingWindow( + bConsiderSnackbar = true, + bTop = false, + bBottom = true + ) } - } else{ + } else { adjustPositionOfFloatingWindow(bConsiderSnackbar = true, bTop = false, bBottom = true) } } @@ -640,6 +643,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn } } + logDebug("Chat ID of the call is $chatId") initFloatingPanel() val meetingName: String = args.meetingName @@ -832,7 +836,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn } individualCallFragment?.let { - if(it.isAdded){ + if (it.isAdded) { it.updateOrientation() } } @@ -920,11 +924,30 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn } private fun initToolbar() { + logDebug("Update toolbar elements") + val root = meetingActivity.binding.root toolbar = meetingActivity.binding.toolbar toolbarTitle = meetingActivity.binding.titleToolbar toolbarSubtitle = meetingActivity.binding.subtitleToolbar - toolbarSubtitle?.let { - it.text = StringResourcesUtils.getString(R.string.chat_connecting) + + root.apply { + setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.grey_900)) + } + + toolbar.apply { + background = ContextCompat.getDrawable( + requireContext(), R.drawable.gradient_shape_callschat + ) + setBackgroundColor(Color.TRANSPARENT) + } + + toolbarTitle?.apply { + setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) + } + + toolbarSubtitle?.apply { + text = StringResourcesUtils.getString(R.string.chat_connecting) + setTextColor(ContextCompat.getColor(requireContext(), R.color.white)) } bannerAnotherCallLayout = meetingActivity.binding.bannerAnotherCall @@ -949,6 +972,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn } else { actionBar.setHomeButtonEnabled(false) actionBar.setDisplayHomeAsUpEnabled(false) + actionBar.setHomeAsUpIndicator(R.drawable.ic_close_white) } setHasOptionsMenu(true) @@ -1075,29 +1099,31 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn } sharedModel.snackBarLiveData.observe(viewLifecycleOwner) { - showSnackbar(SNACKBAR_TYPE, it, MEGACHAT_INVALID_HANDLE) - if (bInviteSent) { - bInviteSent = false - val count = inMeetingViewModel.participants.value - if (count != null) { - val participantsCount = getParticipantsCount() - if (participantsCount == 0L && count.size == 0) { - if (STATE_FINISH != inMeetingViewModel.shouldShowWarningMessage()) { - logDebug("launchTimer() for no participant join in after Invite") - inMeetingViewModel.updateShowWarningMessage(STATE_INVITE) - launchTimer() - } - } else if (participantsCount > 0 && count.size < participantsCount.toInt()) { - if (STATE_FINISH != inMeetingViewModel.shouldShowWarningMessage()) { - logDebug("launchTimer() for not all participants join in after Invite") - inMeetingViewModel.updateShowWarningMessage(STATE_INVITE) - launchTimer() + if (it.isNotEmpty()) { + showSnackbar(SNACKBAR_TYPE, it, MEGACHAT_INVALID_HANDLE) + if (bInviteSent) { + bInviteSent = false + val count = inMeetingViewModel.participants.value + if (count != null) { + val participantsCount = getParticipantsCount() + if (participantsCount == 0L && count.size == 0) { + if (STATE_FINISH != inMeetingViewModel.shouldShowWarningMessage()) { + logDebug("launchTimer() for no participant join in after Invite") + inMeetingViewModel.updateShowWarningMessage(STATE_INVITE) + launchTimer() + } + } else if (participantsCount > 0 && count.size < participantsCount.toInt()) { + if (STATE_FINISH != inMeetingViewModel.shouldShowWarningMessage()) { + logDebug("launchTimer() for not all participants join in after Invite") + inMeetingViewModel.updateShowWarningMessage(STATE_INVITE) + launchTimer() + } + } else { + logDebug("noting to do") } - } else { - logDebug("noting to do") } - } + } } } @@ -1236,7 +1262,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn * * @param outMetrics display metrics */ - private fun onConfigurationChangedOfFloatingWindow(outMetrics: DisplayMetrics ) { + private fun onConfigurationChangedOfFloatingWindow(outMetrics: DisplayMetrics) { floatingWindowContainer.post { previousY = -1f val dx = outMetrics.widthPixels - floatingWindowContainer.width @@ -1250,6 +1276,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn floatingWindowContainer.moveY(dy.toFloat()) } } + /** * Method to control the position of the floating window in relation to the toolbar, bottom sheet panel and Snackbar * @@ -1257,7 +1284,11 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn * @param bTop Calculate the position related to top * @param bBottom Calculate the position related to bottom */ - private fun adjustPositionOfFloatingWindow(bConsiderSnackbar: Boolean, bTop: Boolean = false, bBottom: Boolean = true) { + private fun adjustPositionOfFloatingWindow( + bConsiderSnackbar: Boolean, + bTop: Boolean = false, + bBottom: Boolean = true + ) { var isIntersect: Boolean var isIntersectPreviously: Boolean @@ -1350,7 +1381,12 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn val anotherCall = inMeetingViewModel.getAnotherCall() if (anotherCall != null) { logDebug("Return to another call") - CallUtil.openMeetingInProgress(requireContext(), anotherCall.chatid, false, passcodeManagement) + CallUtil.openMeetingInProgress( + requireContext(), + anotherCall.chatid, + false, + passcodeManagement + ) } } @@ -1438,7 +1474,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn private fun checkCurrentParticipants() { inMeetingViewModel.getCall()?.let { logDebug("Check current call participants") - inMeetingViewModel.createCurrentParticipants(it.sessionsClientid, status) + inMeetingViewModel.createCurrentParticipants(it.sessionsClientid) } } @@ -1446,6 +1482,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn * Method to remove all video listeners and all child fragments */ private fun removeListenersAndFragments() { + logDebug("Remove listeners and fragments") removeAllListeners() removeAllFragments() } @@ -1521,11 +1558,11 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn * Show reconnecting UI */ private fun reconnecting() { - logDebug("Show reconnecting UI") - if (status == NOT_TYPE) + logDebug("Show reconnecting UI, the current status is ${inMeetingViewModel.status}") + if (inMeetingViewModel.status == NOT_TYPE) return - status = NOT_TYPE + inMeetingViewModel.status = NOT_TYPE removeListenersAndFragments() binding.reconnecting.isVisible = true @@ -1536,10 +1573,11 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn * Remove fragments */ fun removeUI() { - if (status == NOT_TYPE) + logDebug("Removing call UI, the current status is ${inMeetingViewModel.status}") + if (inMeetingViewModel.status == NOT_TYPE) return - status = NOT_TYPE + inMeetingViewModel.status = NOT_TYPE removeListenersAndFragments() } @@ -1576,7 +1614,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn * Show one to one call UI */ private fun initOneToOneCall() { - if (status == TYPE_IN_ONE_TO_ONE) return + if (inMeetingViewModel.status == TYPE_IN_ONE_TO_ONE) return removeListenersAndFragments() @@ -1585,7 +1623,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn val session = inMeetingViewModel.getSessionOneToOneCall(currentCall) session?.let { userSession -> logDebug("Show one to one call UI") - status = TYPE_IN_ONE_TO_ONE + inMeetingViewModel.status = TYPE_IN_ONE_TO_ONE checkInfoBanner(TYPE_SINGLE_PARTICIPANT) logDebug("Create fragment") @@ -1614,10 +1652,10 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn * @param chatId ID of chat */ private fun waitingForConnection(chatId: Long) { - if (status == TYPE_WAITING_CONNECTION) return + if (inMeetingViewModel.status == TYPE_WAITING_CONNECTION) return logDebug("Show waiting for connection call UI") - status = TYPE_WAITING_CONNECTION + inMeetingViewModel.status = TYPE_WAITING_CONNECTION checkInfoBanner(TYPE_SINGLE_PARTICIPANT) removeListenersAndFragments() @@ -1671,7 +1709,11 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn // Calculate the position of floating window if it is shown after the snack bar. meetingActivity.snackbar?.let { floatingWindowContainer.post { - adjustPositionOfFloatingWindow(bConsiderSnackbar = true, bTop = false, bBottom = true) + adjustPositionOfFloatingWindow( + bConsiderSnackbar = true, + bTop = false, + bBottom = true + ) } } } @@ -1680,10 +1722,10 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn * Method to display the speaker view UI */ private fun initSpeakerViewMode() { - if (status == TYPE_IN_SPEAKER_VIEW) return + if (inMeetingViewModel.status == TYPE_IN_SPEAKER_VIEW) return logDebug("Show group call - Speaker View UI") - status = TYPE_IN_SPEAKER_VIEW + inMeetingViewModel.status = TYPE_IN_SPEAKER_VIEW checkInfoBanner(TYPE_SINGLE_PARTICIPANT) inMeetingViewModel.removeAllParticipantVisible() @@ -1712,7 +1754,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn ) } - inMeetingViewModel.updateParticipantResolution(status) + inMeetingViewModel.updateParticipantResolution() checkGridSpeakerViewMenuItemVisibility() } @@ -1720,10 +1762,10 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn * Method to display the grid view UI */ private fun initGridViewMode() { - if (status == TYPE_IN_GRID_VIEW) return + if (inMeetingViewModel.status == TYPE_IN_GRID_VIEW) return logDebug("Show group call - Grid View UI") - status = TYPE_IN_GRID_VIEW + inMeetingViewModel.status = TYPE_IN_GRID_VIEW checkInfoBanner(TYPE_SINGLE_PARTICIPANT) inMeetingViewModel.removeAllParticipantVisible() @@ -1753,7 +1795,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn ) } - inMeetingViewModel.updateParticipantResolution(status) + inMeetingViewModel.updateParticipantResolution() checkGridSpeakerViewMenuItemVisibility() } @@ -1763,7 +1805,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn * @param chatId the chat ID */ private fun initGroupCall(chatId: Long) { - if (status != TYPE_IN_GRID_VIEW && status != TYPE_IN_SPEAKER_VIEW) { + if (inMeetingViewModel.status != TYPE_IN_GRID_VIEW && inMeetingViewModel.status != TYPE_IN_SPEAKER_VIEW) { individualCallFragment?.let { if (it.isAdded) { it.removeChatVideoListener() @@ -1786,7 +1828,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn } } } - status == TYPE_IN_SPEAKER_VIEW -> { + inMeetingViewModel.status == TYPE_IN_SPEAKER_VIEW -> { logDebug("Manual mode - Speaker view") initSpeakerViewMode() } @@ -1956,7 +1998,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn } } - when (status) { + when (inMeetingViewModel.status) { TYPE_IN_GRID_VIEW -> { gridViewMenuItem?.let { it.isVisible = false @@ -2037,7 +2079,8 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn BottomFloatingPanelViewHolder( inMeetingViewModel, binding, - this + this, + resources.displayMetrics ) updatePanelParticipantList() @@ -2092,6 +2135,20 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn } } + /** + * Change Bottom Floating Panel State + * + */ + override fun onChangePanelState() { + if(isWaitingForMakeModerator){ + if (bottomFloatingPanelViewHolder.getState() == BottomSheetBehavior.STATE_COLLAPSED) { + findNavController().navigate( + InMeetingFragmentDirections.actionGlobalMakeModerator() + ) + } + } + } + /** * Change Mic State * @@ -2235,7 +2292,11 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn // Delay a bit to wait for 'bannerMuteLayout' finish layouting, otherwise, its bottom is 0. RunOnUIThreadUtils.runDelay(10) { - adjustPositionOfFloatingWindow(bConsiderSnackbar = false, bTop = true, bBottom = false) + adjustPositionOfFloatingWindow( + bConsiderSnackbar = false, + bTop = true, + bBottom = false + ) } } } @@ -2467,7 +2528,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn } } - inMeetingViewModel.updateParticipantResolution(status) + inMeetingViewModel.updateParticipantResolution() } /** @@ -2547,7 +2608,12 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn logDebug("Change of status on hold and switch of call") inMeetingViewModel.setCallOnHold(true) inMeetingViewModel.setAnotherCallOnHold(anotherCall.chatid, false) - CallUtil.openMeetingInProgress(requireContext(), anotherCall.chatid, false, passcodeManagement) + CallUtil.openMeetingInProgress( + requireContext(), + anotherCall.chatid, + false, + passcodeManagement + ) } inMeetingViewModel.isCallOnHold() -> { logDebug("Change of status on hold") @@ -2606,9 +2672,20 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn leaveMeeting() } + /** + * Method to navigate to the Make moderator screen + */ private val showAssignModeratorFragment = fun() { - AssignModeratorBottomFragment.newInstance(leaveMeetingModerator).let { - it.show(childFragmentManager, it.tag) + var isPanelExpanded = + bottomFloatingPanelViewHolder.getState() == BottomSheetBehavior.STATE_EXPANDED + isWaitingForMakeModerator = isPanelExpanded + + if (isPanelExpanded) { + bottomFloatingPanelViewHolder.collapse() + } else { + findNavController().navigate( + InMeetingFragmentDirections.actionGlobalMakeModerator() + ) } } @@ -2635,28 +2712,12 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn inMeetingViewModel.leaveMeeting() if (inMeetingViewModel.amIAGuest()) { - finishActivityAsGuest() + inMeetingViewModel.finishActivityAsGuest(meetingActivity) } else { - checkIfAnotherCallShouldBeShown() + inMeetingViewModel.checkIfAnotherCallShouldBeShown(passcodeManagement) } } - /** - * Method to control when I am a guest and my participation in the meeting ends - */ - private fun finishActivityAsGuest() { - val chatId = inMeetingViewModel.getChatId() - val callId = inMeetingViewModel.getCall()?.callId - logDebug("Finishing the activity as guest: chatId $chatId, callId $callId") - if (chatId != MEGACHAT_INVALID_HANDLE && callId != MEGACHAT_INVALID_HANDLE) { - MegaApplication.getChatManagement().controlCallFinished(callId!!, chatId) - } - AccountController.logout( - meetingActivity, - MegaApplication.getInstance().megaApi - ) - } - /** * Method to control when call ended */ @@ -2738,7 +2799,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn MeetingParticipantBottomSheetDialogFragment.newInstance( inMeetingViewModel.amIAGuest(), inMeetingViewModel.isModerator(), - status == TYPE_IN_SPEAKER_VIEW, + inMeetingViewModel.status == TYPE_IN_SPEAKER_VIEW, participant ) participantBottomSheet.show(childFragmentManager, participantBottomSheet.tag) @@ -2787,6 +2848,13 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn override fun onDestroy() { super.onDestroy() + sharedModel.hideSnackBar() + inMeetingViewModel.hideSnackBarWarningMsg() + bannerAnotherCallLayout.isVisible = false + bannerParticipant?.isVisible = false + bannerInfo?.isVisible = false + bannerMuteLayout.isVisible = false + removeUI() logDebug("Fragment destroyed") CallUtil.activateChrono(false, meetingChrono, null) @@ -2874,6 +2942,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn } override fun onJoinedChat(chatId: Long, userHandle: Long) { + logDebug("Joined to the chat") controlWhenJoinedAChat(chatId) } @@ -2909,17 +2978,6 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn ) } - /** - * Perform the necessary actions when the call is over. - * Check if exists another call in progress or on hold - */ - private fun checkIfAnotherCallShouldBeShown() { - inMeetingViewModel.getAnotherCall()?.let { - logDebug("Show another call") - CallUtil.openMeetingInProgress(requireContext(), it.chatid, false, passcodeManagement) - } - } - /** * The dialog for alerting the meeting is failed to created */ @@ -2957,5 +3015,7 @@ class InMeetingFragment : MeetingBaseFragment(), BottomFloatingPanelListener, Sn } } - override fun onCallFailed(chatId: Long) {} + override fun onCallFailed(chatId: Long) { + logError("Call with chat ID $chatId failed") + } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/meeting/fragments/InMeetingRepository.kt b/app/src/main/java/mega/privacy/android/app/meeting/fragments/InMeetingRepository.kt index 92c6588a176..34195715f59 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/fragments/InMeetingRepository.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/fragments/InMeetingRepository.kt @@ -469,18 +469,6 @@ class InMeetingRepository @Inject constructor( ) } - fun updateChatPermissions( - chatId: Long, peerId: Long, - listener: MegaChatRequestListenerInterface? - ) { - megaChatApi.updateChatPermissions( - chatId, - peerId, - MegaChatRoom.PRIV_MODERATOR, - listener - ) - } - /** * Method for getting a participant's avatar * diff --git a/app/src/main/java/mega/privacy/android/app/meeting/fragments/InMeetingViewModel.kt b/app/src/main/java/mega/privacy/android/app/meeting/fragments/InMeetingViewModel.kt index c62d9b418e0..9b12bf512b7 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/fragments/InMeetingViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/fragments/InMeetingViewModel.kt @@ -24,12 +24,14 @@ import mega.privacy.android.app.constants.EventConstants.EVENT_UPDATE_CALL import mega.privacy.android.app.fragments.homepage.Event import mega.privacy.android.app.listeners.EditChatRoomNameListener import mega.privacy.android.app.listeners.GetUserEmailListener +import mega.privacy.android.app.lollipop.controllers.AccountController import mega.privacy.android.app.lollipop.controllers.ChatController import mega.privacy.android.app.lollipop.listeners.CreateGroupChatWithPublicLink import mega.privacy.android.app.meeting.adapter.Participant import mega.privacy.android.app.meeting.fragments.InMeetingFragment.Companion.TYPE_IN_GRID_VIEW import mega.privacy.android.app.meeting.fragments.InMeetingFragment.Companion.TYPE_IN_SPEAKER_VIEW import mega.privacy.android.app.meeting.listeners.* +import mega.privacy.android.app.objects.PasscodeManagement import mega.privacy.android.app.utils.CallUtil import mega.privacy.android.app.utils.ChatUtil.getTitleChat import mega.privacy.android.app.utils.Constants.* @@ -46,6 +48,8 @@ class InMeetingViewModel @ViewModelInject constructor( ) : ViewModel(), EditChatRoomNameListener.OnEditedChatRoomNameCallback, HangChatCallListener.OnCallHungUpCallback, GetUserEmailListener.OnUserEmailUpdateCallback { + var status = InMeetingFragment.NOT_TYPE + var currentChatId: Long = MEGACHAT_INVALID_HANDLE var previousState: Int = CALL_STATUS_INITIAL @@ -55,7 +59,7 @@ class InMeetingViewModel @ViewModelInject constructor( private var currentTimer = WAITING_TIME var countDownTimer: CountDownTimer? = null - private val _updateUI: MutableLiveData = MutableLiveData() + private val _updateUI: MutableLiveData = MutableLiveData(false) val updateUI: LiveData = _updateUI private var haveConnection: Boolean = false @@ -898,15 +902,14 @@ class InMeetingViewModel @ViewModelInject constructor( * Method for creating participants already on the call * * @param list list of participants - * @param status if it's grid view or speaker view */ - fun createCurrentParticipants(list: MegaHandleList?, status: String) { + fun createCurrentParticipants(list: MegaHandleList?) { list?.let { listParticipants -> if (listParticipants.size() > 0) { _callLiveData.value = inMeetingRepository.getMeeting(currentChatId) for (i in 0 until list.size()) { getSession(list[i])?.let { session -> - createParticipant(session, status)?.let { participantCreated -> + createParticipant(session)?.let { participantCreated -> logDebug("Adding current participant... ${participantCreated.clientId}") participants.value?.add(participantCreated) } @@ -925,8 +928,8 @@ class InMeetingViewModel @ViewModelInject constructor( * @param session MegaChatSession of a participant * @return the position of the participant */ - fun addParticipant(session: MegaChatSession, status: String): Int? { - createParticipant(session, status)?.let { participantCreated -> + fun addParticipant(session: MegaChatSession): Int? { + createParticipant(session)?.let { participantCreated -> participants.value?.add(participantCreated) logDebug("Adding participant... ${participantCreated.clientId}") participants.value = participants.value @@ -943,7 +946,7 @@ class InMeetingViewModel @ViewModelInject constructor( * @param session MegaChatSession of a participant * @return the position of the participant */ - private fun createParticipant(session: MegaChatSession, status: String): Participant? { + private fun createParticipant(session: MegaChatSession): Participant? { inMeetingRepository.getChatRoom(currentChatId)?.let { participants.value?.let { listParticipants -> val peer = listParticipants.filter { participant -> @@ -1525,10 +1528,8 @@ class InMeetingViewModel @ViewModelInject constructor( * * In Speaker view, the list of participants should have low res * In Grid view, if there is more than 4, low res. Hi res in the opposite case - * - * @param status if it's Speaker view or Grid view */ - fun updateParticipantResolution(status: String) { + fun updateParticipantResolution() { logDebug("Changing the resolution of participants when the UI changes") participants.value?.let { listParticipants -> val iterator = listParticipants.iterator() @@ -1854,19 +1855,6 @@ class InMeetingViewModel @ViewModelInject constructor( fun isModerator(): Boolean = getOwnPrivileges() == MegaChatRoom.PRIV_MODERATOR - /** - * Method for updating a participant's permissions - * - * @param peerId User handle of a participant - * @param listener MegaChatRequestListenerInterface - */ - fun updateChatPermissions( - peerId: Long, - listener: MegaChatRequestListenerInterface? = null - ) { - inMeetingRepository.updateChatPermissions(currentChatId, peerId, listener) - } - /** * Method for obtaining the bitmap of a participant's avatar * @@ -1924,6 +1912,13 @@ class InMeetingViewModel @ViewModelInject constructor( } } + /** + * Hide snack bar + */ + fun hideSnackBarWarningMsg() { + _updateUI.value = false + } + /** * Get current timer for countdown */ @@ -2001,7 +1996,7 @@ class InMeetingViewModel @ViewModelInject constructor( .filter { it.isModerator && it.name.isNotEmpty() } .map { it.name } .forEach { - nameList = if (nameList.isNotEmpty()) "$nameList, $it" else "$it" + nameList = if (nameList.isNotEmpty()) "$nameList, $it" else it } return nameList @@ -2202,4 +2197,46 @@ class InMeetingViewModel @ViewModelInject constructor( return null } + + /** + * Perform the necessary actions when the call is over. + * Check if exists another call in progress or on hold + */ + fun checkIfAnotherCallShouldBeShown(passcodeManagement: PasscodeManagement): Boolean { + getAnotherCall()?.let { + logDebug("Show another call") + CallUtil.openMeetingInProgress( + MegaApplication.getInstance().applicationContext, + it.chatid, + false, + passcodeManagement + ) + return true + } + + return false + } + + /** + * Method to know if local video is activated + * + * @return True, if it's on. False, if it's off + */ + fun isLocalCameraOn(): Boolean = getCall()?.hasLocalVideo() ?: false + + /** + * Method to control when I am a guest and my participation in the meeting ends + */ + fun finishActivityAsGuest(meetingActivity: Context) { + val chatId = getChatId() + val callId = getCall()?.callId + logDebug("Finishing the activity as guest: chatId $chatId, callId $callId") + if (chatId != MEGACHAT_INVALID_HANDLE && callId != MEGACHAT_INVALID_HANDLE) { + MegaApplication.getChatManagement().controlCallFinished(callId!!, chatId) + } + AccountController.logout( + meetingActivity, + MegaApplication.getInstance().megaApi + ) + } } \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/meeting/fragments/MakeModeratorFragment.kt b/app/src/main/java/mega/privacy/android/app/meeting/fragments/MakeModeratorFragment.kt new file mode 100644 index 00000000000..cb36fea5fd6 --- /dev/null +++ b/app/src/main/java/mega/privacy/android/app/meeting/fragments/MakeModeratorFragment.kt @@ -0,0 +1,349 @@ +package mega.privacy.android.app.meeting.fragments + +import android.graphics.Color +import android.os.Bundle +import android.util.Pair +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Observer +import androidx.navigation.fragment.findNavController +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.appbar.MaterialToolbar +import com.jeremyliao.liveeventbus.LiveEventBus +import dagger.hilt.android.AndroidEntryPoint +import mega.privacy.android.app.MegaApplication +import mega.privacy.android.app.R +import mega.privacy.android.app.components.PositionDividerItemDecoration +import mega.privacy.android.app.components.twemoji.EmojiTextView +import mega.privacy.android.app.constants.EventConstants +import mega.privacy.android.app.databinding.MakeModeratorFragmentBinding +import mega.privacy.android.app.meeting.activity.MeetingActivity +import mega.privacy.android.app.meeting.adapter.AssignParticipantsAdapter +import mega.privacy.android.app.meeting.adapter.Participant +import mega.privacy.android.app.meeting.adapter.SelectedParticipantsAdapter +import mega.privacy.android.app.objects.PasscodeManagement +import mega.privacy.android.app.utils.ColorUtils +import mega.privacy.android.app.utils.LogUtil.logDebug +import mega.privacy.android.app.utils.StringResourcesUtils +import mega.privacy.android.app.utils.Util +import nz.mega.sdk.MegaChatApiJava.MEGACHAT_INVALID_HANDLE +import nz.mega.sdk.MegaChatCall +import nz.mega.sdk.MegaChatSession +import org.jetbrains.anko.displayMetrics +import java.util.* +import javax.inject.Inject + +@AndroidEntryPoint +class MakeModeratorFragment : MeetingBaseFragment() { + private lateinit var binding: MakeModeratorFragmentBinding + private var selectedParticipants: MutableList = mutableListOf() + private var participants: MutableList = mutableListOf() + + private lateinit var selectedParticipantsAdapter: SelectedParticipantsAdapter + private lateinit var participantsAdapter: AssignParticipantsAdapter + + private lateinit var itemDecoration: PositionDividerItemDecoration + private var chatId: Long? = MEGACHAT_INVALID_HANDLE + + // Views + lateinit var toolbar: MaterialToolbar + lateinit var toolbarTitle: EmojiTextView + lateinit var toolbarSubtitle: TextView + val inMeetingViewModel: InMeetingViewModel by activityViewModels() + + @Inject + lateinit var passcodeManagement: PasscodeManagement + + private val callStatusObserver = Observer { + if (inMeetingViewModel.isSameCall(it.callId)) { + when (it.status) { + MegaChatCall.CALL_STATUS_TERMINATING_USER_PARTICIPATION, + MegaChatCall.CALL_STATUS_DESTROYED -> { + disableLocalCamera() + finishActivity() + } + } + } + } + + private val sessionStatusObserver = + Observer> { callAndSession -> + if (inMeetingViewModel.isSameCall(callAndSession.first) && !inMeetingViewModel.isOneToOneCall()) { + when (callAndSession.second.status) { + MegaChatSession.SESSION_STATUS_IN_PROGRESS -> { + logDebug("Session in progress, clientID = ${callAndSession.second.clientid}") + inMeetingViewModel.addParticipant(callAndSession.second) + } + MegaChatSession.SESSION_STATUS_DESTROYED -> { + logDebug("Session destroyed, clientID = ${callAndSession.second.clientid}") + inMeetingViewModel.removeParticipant(callAndSession.second) + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = MakeModeratorFragmentBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + logDebug("In make moderator fragment") + chatId = arguments?.getLong(MeetingActivity.MEETING_CHAT_ID, MEGACHAT_INVALID_HANDLE) + if (chatId == MEGACHAT_INVALID_HANDLE) { + sharedModel.currentChatId.value?.let { + chatId = it + } + } + + setupView() + initLiveEvent() + + inMeetingViewModel.participants.observe(viewLifecycleOwner) { participants -> + participants?.let { + update(participants.filter { inMeetingViewModel.isStandardUser(it.peerId) && !it.isGuest } + .map { it.copy() } + .toMutableList()) + } + } + } + + /** + * Method for initialising UI elements + */ + private fun initLiveEvent() { + //Sessions Level + @Suppress("UNCHECKED_CAST") + LiveEventBus.get(EventConstants.EVENT_SESSION_STATUS_CHANGE) + .observe(this, sessionStatusObserver as Observer) + + LiveEventBus.get(EventConstants.EVENT_CALL_STATUS_CHANGE, MegaChatCall::class.java) + .observe(this, callStatusObserver) + } + + /** + * Method for initialising UI elements + */ + private fun setupView() { + logDebug("Update toolbar elements"); + binding.btCancel.setOnClickListener { cancel() } + binding.btOk.setOnClickListener { makeModerators() } + + val root = meetingActivity.binding.root + root.apply { + setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.white_black)) + } + toolbar = meetingActivity.binding.toolbar + + toolbar.apply { + background = null + setBackgroundColor(Color.TRANSPARENT) + } + + toolbarTitle = meetingActivity.binding.titleToolbar + toolbarTitle.apply { + text = StringResourcesUtils.getString(R.string.assign_moderator).toUpperCase( + Locale.getDefault() + ) + + setTextColor(ContextCompat.getColor(requireContext(), R.color.black_white)) + } + + toolbarSubtitle = meetingActivity.binding.subtitleToolbar + + toolbarSubtitle.apply { + text = StringResourcesUtils.getString(R.string.pick_new_moderator_message) + setTextColor(ContextCompat.getColor(requireContext(), R.color.grey_300)) + } + + meetingActivity.setSupportActionBar(toolbar) + val actionBar = meetingActivity.supportActionBar ?: return + actionBar.apply { + title = null + setHomeButtonEnabled(true) + setDisplayHomeAsUpEnabled(true) + setHomeAsUpIndicator( + ColorUtils.tintIcon( + requireContext(), + R.drawable.ic_close_white, + ContextCompat.getColor(requireContext(), R.color.black_white) + ) + ) + } + + setHasOptionsMenu(true) + initRecyclerview() + } + + private fun initRecyclerview() { + participantsAdapter = AssignParticipantsAdapter(sharedModel, selectCallback) + selectedParticipantsAdapter = + SelectedParticipantsAdapter(sharedModel, deleteCallback) + + itemDecoration = + PositionDividerItemDecoration(context, MegaApplication.getInstance().displayMetrics) + itemDecoration.setDrawAllDividers(true) + + binding.participantList.apply { + layoutManager = LinearLayoutManager(context) + itemAnimator = Util.noChangeRecyclerViewItemAnimator() + clipToPadding = false + adapter = participantsAdapter + addItemDecoration(itemDecoration) + } + + binding.selectedParticipantList.apply { + layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + itemAnimator = null + clipToPadding = false + adapter = selectedParticipantsAdapter + } + + participantsAdapter.submitList(participants.toList()) + } + + /** + * The call back function when users select participants, will update the selected list and participant list + */ + private val selectCallback = fun(position: Int) { + updateParticipantList(position) + + val participant = participants[position] + + if (participant.isChosenForAssign) { + selectedParticipants.add(participant) + } else { + selectedParticipants.remove(participant) + } + + updateSelectedParticipant() + } + + /** + * The call back function when users delete participants, will update the selected list and participant list + */ + private val deleteCallback = fun(participant: Participant) { + val position = participants.indexOf(participant) + + selectedParticipants.remove(participant) + updateSelectedParticipant() + updateParticipantList(position) + } + + /** + * Update the participant list when user choose or delete the participant + */ + private fun updateParticipantList(position: Int) { + val participant = participants[position] + participant.isChosenForAssign = !participant.isChosenForAssign + participants[position] = participant + + participantsAdapter.notifyItemChanged(position) + } + + /** + * Update the selected participant list when user choose or delete the participant + */ + private fun updateSelectedParticipant() { + if (selectedParticipants.size > 0) { + toolbarSubtitle.text = + StringResourcesUtils.getString(R.string.selected_items, selectedParticipants.size) + binding.btOk.isEnabled = true + } else { + toolbarSubtitle.text = + StringResourcesUtils.getString(R.string.pick_new_moderator_message) + binding.btOk.isEnabled = false + } + + binding.rlSelectedParticipantList.isVisible = selectedParticipants.size > 0 + + selectedParticipantsAdapter.submitList(selectedParticipants.toMutableList()) + } + + /** + * Update the participant list and selected list when someone leave this meeting + * + * @param participantsList new participant list + */ + fun update(participantsList: MutableList) { + // Get the current selected id + participants = participantsList + val oldSelect = selectedParticipants.map { it.peerId } + participants.forEach { + if (oldSelect.contains(it.peerId)) { + it.isChosenForAssign = true + } + } + + participantsAdapter.submitList(participants.toMutableList()) + + val newSelect = participants.filter { it.isChosenForAssign }.map { it.peerId } + selectedParticipants.run { + removeIf { + !newSelect.contains(it.peerId) + } + forEach { + it.isChosenForAssign = true + } + } + + selectedParticipantsAdapter.submitList(selectedParticipants.toMutableList()) + } + + /** + * Make selected participants to moderator + */ + private fun makeModerators() { + // Get the list and assign the user in the list to moderator + selectedParticipants.forEach { + sharedModel.giveModeratorPermissions(it.peerId) + } + + disableLocalCamera() + logDebug("Leave meeting") + inMeetingViewModel.leaveMeeting() + finishActivity() + } + + /** + * Method to control how the meeting activity should finish correctly + */ + fun finishActivity() { + if (inMeetingViewModel.amIAGuest()) { + inMeetingViewModel.finishActivityAsGuest(meetingActivity) + } else if (!inMeetingViewModel.checkIfAnotherCallShouldBeShown(passcodeManagement)) { + logDebug("Finish meeting activity") + meetingActivity.finish() + } + } + + /** + * Method to disable the local camera + */ + fun disableLocalCamera() { + if (inMeetingViewModel.isLocalCameraOn()) { + logDebug("Disable local camera") + sharedModel.clickCamera(false) + } + } + + /** + * Cancel this action and close this page + */ + fun cancel() { + val action = MakeModeratorFragmentDirections.actionGlobalInMeeting( + action = MeetingActivity.MEETING_ACTION_IN, + chatId = inMeetingViewModel.getChatId() + ) + findNavController().navigate(action) + } +} \ No newline at end of file diff --git a/app/src/main/java/mega/privacy/android/app/meeting/fragments/MeetingBaseFragment.kt b/app/src/main/java/mega/privacy/android/app/meeting/fragments/MeetingBaseFragment.kt index a39a95edda5..ed78c982a4f 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/fragments/MeetingBaseFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/fragments/MeetingBaseFragment.kt @@ -25,6 +25,7 @@ import mega.privacy.android.app.utils.permission.permissionsBuilder * [JoinMeetingAsGuestFragment], * [JoinMeetingFragment], * [InMeetingFragment], + * [MakeModeratorFragment], * [RingingMeetingFragment], * [IndividualCallFragment], * [GridViewCallFragment], diff --git a/app/src/main/java/mega/privacy/android/app/meeting/fragments/MeetingParticipantBottomSheetDialogFragment.kt b/app/src/main/java/mega/privacy/android/app/meeting/fragments/MeetingParticipantBottomSheetDialogFragment.kt index 8eeb9d1cc14..5cf46fd701b 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/fragments/MeetingParticipantBottomSheetDialogFragment.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/fragments/MeetingParticipantBottomSheetDialogFragment.kt @@ -105,7 +105,7 @@ class MeetingParticipantBottomSheetDialogFragment : BaseBottomSheetDialogFragmen listenAction(binding.makeModerator) { // Make moderator - inMeetingViewModel.updateChatPermissions( + sharedViewModel.giveModeratorPermissions( participantItem.peerId ) } @@ -181,7 +181,7 @@ class MeetingParticipantBottomSheetDialogFragment : BaseBottomSheetDialogFragmen * @param participant the target participant */ private fun initAvatar(participant: Participant) { - binding.avatar.setImageBitmap(inMeetingViewModel.getAvatarBitmapByPeerId(participant.peerId)) + binding.avatar.setImageBitmap(sharedViewModel.getAvatarBitmapByPeerId(participant.peerId)) } companion object { diff --git a/app/src/main/java/mega/privacy/android/app/meeting/fragments/MeetingParticipantBottomSheetDialogViewModel.kt b/app/src/main/java/mega/privacy/android/app/meeting/fragments/MeetingParticipantBottomSheetDialogViewModel.kt index c42a33a8e1a..4a5c06bfa72 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/fragments/MeetingParticipantBottomSheetDialogViewModel.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/fragments/MeetingParticipantBottomSheetDialogViewModel.kt @@ -88,13 +88,38 @@ class MeetingParticipantBottomSheetDialogViewModel @ViewModelInject constructor( fun showContactInfoOrEditProfile(): Boolean = !isGuest && !isParticipantGuest() && (participant?.isContact == true || participant?.isMe == true) - /** * Determine if show the divider between info item and option items * * @return if should show return true, else false */ - fun showDividerContactInfo(): Boolean = showAddContact() || showContactInfoOrEditProfile() + fun showDividerContactInfo(): Boolean = (showAddContact() || showContactInfoOrEditProfile()) + && (showSendMessage() || showPinItem() || showMakeModeratorItem() || showRemoveItem()) + + + /** + * Determine if show the divider between seng message item and option items + * + * @return if should show return true, else false + */ + fun showDividerSendMessage(): Boolean = + showSendMessage() && (showPinItem() || showMakeModeratorItem() || showRemoveItem()) + + + /** + * Determine if show the divider between ping to speaker item and option items + * + * @return if should show return true, else false + */ + fun showDividerPingToSpeaker(): Boolean = + showPinItem() && (showMakeModeratorItem() || showRemoveItem()) + + /** + * Determine if show the divider between make moderator item and option items + * + * @return if should show return true, else false + */ + fun showDividerMakeModerator(): Boolean = showMakeModeratorItem() && showRemoveItem() /** * Determine if show the `Edit Profile` item diff --git a/app/src/main/java/mega/privacy/android/app/meeting/listeners/BottomFloatingPanelListener.kt b/app/src/main/java/mega/privacy/android/app/meeting/listeners/BottomFloatingPanelListener.kt index 9e53f2be656..9e4565db52d 100644 --- a/app/src/main/java/mega/privacy/android/app/meeting/listeners/BottomFloatingPanelListener.kt +++ b/app/src/main/java/mega/privacy/android/app/meeting/listeners/BottomFloatingPanelListener.kt @@ -22,4 +22,9 @@ interface BottomFloatingPanelListener { * Listener for participant item */ fun onParticipantOption(participant: Participant) + + /** + * Listener for bottom panel state + */ + fun onChangePanelState() } 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 d7e1205612d..c134cc09930 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 @@ -350,6 +350,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat optionRemove.setVisibility(View.VISIBLE); optionInfo.setVisibility(View.VISIBLE); + optionFavourite.setVisibility(View.VISIBLE); + optionLabel.setVisibility(View.VISIBLE); //Hide counterOpen--; @@ -405,6 +407,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat offlineSwitch.setChecked(availableOffline(requireContext(), node)); optionInfo.setVisibility(View.VISIBLE); + optionFavourite.setVisibility(View.VISIBLE); + optionLabel.setVisibility(View.VISIBLE); optionRubbishBin.setVisibility(View.VISIBLE); optionLink.setVisibility(View.VISIBLE); @@ -514,6 +518,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat case MegaShare.ACCESS_READ: logDebug("access read"); + optionLabel.setVisibility(View.GONE); + optionFavourite.setVisibility(View.GONE); counterShares--; optionLink.setVisibility(View.GONE); counterShares--; @@ -529,6 +535,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat case MegaShare.ACCESS_READWRITE: logDebug("readwrite"); + optionLabel.setVisibility(View.GONE); + optionFavourite.setVisibility(View.GONE); counterShares--; optionLink.setVisibility(View.GONE); counterShares--; @@ -674,6 +682,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat optionRemove.setVisibility(View.VISIBLE); optionInfo.setVisibility(View.VISIBLE); + optionLabel.setVisibility(View.VISIBLE); + optionFavourite.setVisibility(View.VISIBLE); //Hide counterOpen--; @@ -799,6 +809,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat case MegaShare.ACCESS_READ: logDebug("access read"); + optionLabel.setVisibility(View.GONE); + optionFavourite.setVisibility(View.GONE); counterShares--; optionLink.setVisibility(View.GONE); counterShares--; @@ -814,6 +826,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat case MegaShare.ACCESS_READWRITE: logDebug("readwrite"); + optionLabel.setVisibility(View.GONE); + optionFavourite.setVisibility(View.GONE); counterShares--; optionLink.setVisibility(View.GONE); counterShares--; @@ -899,6 +913,8 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat case MegaShare.ACCESS_READWRITE: case MegaShare.ACCESS_READ: case MegaShare.ACCESS_UNKNOWN: + optionLabel.setVisibility(View.GONE); + optionFavourite.setVisibility(View.GONE); counterModify--; optionRename.setVisibility(View.GONE); counterModify--; diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/RecoveryKeyBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/RecoveryKeyBottomSheetDialogFragment.java index 62c2035486a..b1013012478 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/RecoveryKeyBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/RecoveryKeyBottomSheetDialogFragment.java @@ -17,7 +17,7 @@ public class RecoveryKeyBottomSheetDialogFragment extends BaseBottomSheetDialogF @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - contentView = View.inflate(getContext(), R.layout.bottom_sheet_recovery_key, null); + contentView = View.inflate(requireContext(), R.layout.bottom_sheet_recovery_key, null); itemsLayout = contentView.findViewById(R.id.items_layout); return contentView; } @@ -32,23 +32,21 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat @Override public void onClick(View v) { - AccountController aC = new AccountController(getContext()); - switch(v.getId()){ case R.id.recovery_key_copytoclipboard_layout: - if (getContext() instanceof TwoFactorAuthenticationActivity) { - ((TwoFactorAuthenticationActivity) getContext()).finish(); - } + new AccountController(requireActivity()).copyRkToClipboard(); - aC.copyRkToClipboard(); + if (requireActivity() instanceof TwoFactorAuthenticationActivity) { + ((TwoFactorAuthenticationActivity) requireActivity()).finish(); + } break; case R.id.recovery_key_saveTo_fileSystem_layout: - AccountController.saveRkToFileSystem(getActivity()); + AccountController.saveRkToFileSystem(requireActivity()); break; case R.id.recovery_key_print_layout: - aC.printRK(); + new AccountController(requireActivity()).printRK(); break; } 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 cc6945faffe..7a6712ab5bd 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 @@ -10,6 +10,7 @@ import android.view.View import android.view.ViewGroup import android.widget.TextView import androidx.core.view.isVisible +import com.jeremyliao.liveeventbus.LiveEventBus import dagger.hilt.android.AndroidEntryPoint import mega.privacy.android.app.R import mega.privacy.android.app.databinding.BottomSheetSortByBinding @@ -78,18 +79,13 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { oldOrder = when (orderType) { ORDER_CLOUD -> sortOrderManagement.getOrderCloud() - ORDER_CONTACTS -> sortOrderManagement.getOrderContacts() ORDER_CAMERA -> sortOrderManagement.getOrderCamera() ORDER_OTHERS -> sortOrderManagement.getOrderOthers() + ORDER_OFFLINE -> sortOrderManagement.getOrderOffline() else -> ORDER_DEFAULT_ASC } when (orderType) { - ORDER_CONTACTS -> { - binding.sortByLargestSize.isVisible = false - binding.sortBySmallestSize.isVisible = false - binding.sortBySizeSeparator.isVisible = false - } ORDER_CAMERA -> { binding.sortByNameAsc.isVisible = false binding.sortByNameDesc.isVisible = false @@ -97,7 +93,8 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { binding.sortByLargestSize.isVisible = false binding.sortBySmallestSize.isVisible = false binding.sortBySizeSeparator.isVisible = false - binding.sortByDateSeparator.isVisible = true + binding.sortByFavoritesType.isVisible = false + binding.sortByLabelType.isVisible = false binding.sortByPhotosMediaType.isVisible = true binding.sortByVideosMediaType.isVisible = true } @@ -108,7 +105,11 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { binding.sortBySizeSeparator.isVisible = false binding.sortByNewestDate.isVisible = false binding.sortByOldestDate.isVisible = false + } + ORDER_OFFLINE -> { binding.sortByDateSeparator.isVisible = false + binding.sortByFavoritesType.isVisible = false + binding.sortByLabelType.isVisible = false } } @@ -121,6 +122,8 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { ORDER_MODIFICATION_ASC -> setSelectedColor(binding.sortByOldestDate) ORDER_SIZE_DESC -> setSelectedColor(binding.sortByLargestSize) ORDER_SIZE_ASC -> setSelectedColor(binding.sortBySmallestSize) + ORDER_FAV_ASC -> setSelectedColor(binding.sortByFavoritesType) + ORDER_LABEL_ASC -> setSelectedColor(binding.sortByLabelType) ORDER_PHOTO_DESC -> setSelectedColor(binding.sortByPhotosMediaType) ORDER_VIDEO_DESC -> setSelectedColor(binding.sortByVideosMediaType) } @@ -134,11 +137,11 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { } binding.sortByNewestDate.setOnClickListener { - setNewOrder(if (orderType == ORDER_CONTACTS) ORDER_CREATION_ASC else ORDER_MODIFICATION_DESC) + setNewOrder(ORDER_MODIFICATION_DESC) } binding.sortByOldestDate.setOnClickListener { - setNewOrder(if (orderType == ORDER_CONTACTS) ORDER_CREATION_DESC else ORDER_MODIFICATION_ASC) + setNewOrder(ORDER_MODIFICATION_ASC) } binding.sortByLargestSize.setOnClickListener { @@ -149,6 +152,14 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { setNewOrder(ORDER_SIZE_ASC) } + binding.sortByFavoritesType.setOnClickListener { + setNewOrder(ORDER_FAV_ASC) + } + + binding.sortByLabelType.setOnClickListener { + setNewOrder(ORDER_LABEL_ASC) + } + binding.sortByPhotosMediaType.setOnClickListener { setNewOrder(ORDER_PHOTO_DESC) } @@ -178,6 +189,14 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { when (orderType) { ORDER_CLOUD -> { sortOrderManagement.setOrderCloud(order) + LiveEventBus.get(EVENT_ORDER_CHANGE, Triple::class.java) + .post( + Triple( + order, + sortOrderManagement.getOrderOthers(), + sortOrderManagement.getOrderOffline() + ) + ) if (requireActivity() is ManagerActivityLollipop) { (requireActivity() as ManagerActivityLollipop).refreshCloudOrder(order) @@ -185,9 +204,6 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { updateFileExplorerOrder(order) } } - ORDER_CONTACTS -> { - sortOrderManagement.setOrderContacts(order) - } ORDER_CAMERA -> { sortOrderManagement.setOrderCamera(order) @@ -197,6 +213,14 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { } ORDER_OTHERS -> { sortOrderManagement.setOrderOthers(order) + LiveEventBus.get(EVENT_ORDER_CHANGE, Triple::class.java) + .post( + Triple( + sortOrderManagement.getOrderCloud(), + order, + sortOrderManagement.getOrderOffline() + ) + ) if (requireActivity() is ManagerActivityLollipop) { (requireActivity() as ManagerActivityLollipop).refreshOthersOrder() @@ -204,6 +228,21 @@ class SortByBottomSheetDialogFragment : BaseBottomSheetDialogFragment() { updateFileExplorerOrder(order) } } + ORDER_OFFLINE -> { + sortOrderManagement.setOrderOffline(order) + LiveEventBus.get(EVENT_ORDER_CHANGE, Triple::class.java) + .post( + Triple( + sortOrderManagement.getOrderCloud(), + sortOrderManagement.getOrderOthers(), + order + ) + ) + + if (requireActivity() is ManagerActivityLollipop) { + (requireActivity() as ManagerActivityLollipop).refreshOthersOrder() + } + } } setStateBottomSheetBehaviorHidden() diff --git a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/chatmodalbottomsheet/ParticipantBottomSheetDialogFragment.java b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/chatmodalbottomsheet/ParticipantBottomSheetDialogFragment.java index 4ee58429816..04364606230 100644 --- a/app/src/main/java/mega/privacy/android/app/modalbottomsheet/chatmodalbottomsheet/ParticipantBottomSheetDialogFragment.java +++ b/app/src/main/java/mega/privacy/android/app/modalbottomsheet/chatmodalbottomsheet/ParticipantBottomSheetDialogFragment.java @@ -111,6 +111,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat optionEditProfileChat.setOnClickListener(this); optionLeaveChat.setOnClickListener(this); optionInvite.setOnClickListener(this); + optionStartCall.setOnClickListener(this); optionStartCall.setVisibility(View.GONE); View separatorInfo = contentView.findViewById(R.id.separator_info); View separatorChat = contentView.findViewById(R.id.separator_chat); @@ -183,7 +184,6 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat optionContactInfoChat.setVisibility(View.VISIBLE); optionStartConversationChat.setVisibility(View.VISIBLE); optionStartCall.setVisibility(View.VISIBLE); - optionStartCall.setOnClickListener(participatingInACall() ? null : this); optionInvite.setVisibility(View.GONE); titleMailContactChatPanel.setText(email); @@ -241,9 +241,11 @@ public void onClick(View v) { break; case R.id.contact_list_option_call_layout: - startNewCall((GroupChatInfoActivityLollipop) requireActivity(), - (SnackbarShower) requireActivity(), - megaApi.getContact(chatC.getParticipantEmail(participantHandle)), passcodeManagement); + if (canCallBeStartedFromContactOption((GroupChatInfoActivityLollipop) requireActivity(), passcodeManagement)) { + startNewCall((GroupChatInfoActivityLollipop) requireActivity(), + (SnackbarShower) requireActivity(), + megaApi.getContact(chatC.getParticipantEmail(participantHandle)), passcodeManagement); + } break; case R.id.change_permissions_group_participants_chat_layout: diff --git a/app/src/main/java/mega/privacy/android/app/utils/CallUtil.java b/app/src/main/java/mega/privacy/android/app/utils/CallUtil.java index e3f5ff25c41..7a57ba4813e 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/CallUtil.java +++ b/app/src/main/java/mega/privacy/android/app/utils/CallUtil.java @@ -24,10 +24,10 @@ import androidx.preference.PreferenceManager; import com.google.android.material.dialog.MaterialAlertDialogBuilder; - import java.util.ArrayList; import mega.privacy.android.app.MegaApplication; +import mega.privacy.android.app.OpenLinkActivity; import mega.privacy.android.app.R; import mega.privacy.android.app.interfaces.SnackbarShower; import mega.privacy.android.app.listeners.CreateChatListener; @@ -57,6 +57,7 @@ import static android.content.Context.NOTIFICATION_SERVICE; import static android.view.View.GONE; import static mega.privacy.android.app.meeting.activity.MeetingActivity.*; +import static mega.privacy.android.app.utils.AlertsAndWarnings.showOverDiskQuotaPaywallWarning; import static mega.privacy.android.app.utils.AvatarUtil.*; import static mega.privacy.android.app.utils.ChatUtil.getStatusBitmap; import static mega.privacy.android.app.utils.ChatUtil.getTitleChat; @@ -67,6 +68,7 @@ import static mega.privacy.android.app.utils.StringResourcesUtils.getString; import static mega.privacy.android.app.utils.TextUtil.isTextEmpty; import static mega.privacy.android.app.utils.Util.*; +import static nz.mega.sdk.MegaApiJava.STORAGE_STATE_PAYWALL; import static nz.mega.sdk.MegaChatApiJava.MEGACHAT_INVALID_HANDLE; import static nz.mega.sdk.MegaChatCall.CALL_STATUS_USER_NO_PRESENT; @@ -94,7 +96,7 @@ public static void openMeetingToCreate(Context context) { */ public static void openMeetingToJoin(Context context, long chatId, String meetingName, String link, long publicChatHandle, boolean isRejoin, PasscodeManagement passcodeManagement) { logDebug("Open join a meeting screen:: chatId = " + chatId); - passcodeManagement.setShowPasscodeScreen(false); + passcodeManagement.setShowPasscodeScreen(true); MegaApplication.getChatManagement().setOpeningMeetingLink(chatId, true); Intent meetingIntent = new Intent(context, MeetingActivity.class); if (isRejoin) { @@ -118,7 +120,7 @@ public static void openMeetingToJoin(Context context, long chatId, String meetin */ public static void openMeetingToStart(Context context, long chatId, PasscodeManagement passcodeManagement) { logDebug("Open join a meeting screen. Chat id is " + chatId); - passcodeManagement.setShowPasscodeScreen(false); + passcodeManagement.setShowPasscodeScreen(true); Intent meetingIntent = new Intent(context, MeetingActivity.class); meetingIntent.setAction(MEETING_ACTION_START); meetingIntent.putExtra(MEETING_CHAT_ID, chatId); @@ -134,7 +136,7 @@ public static void openMeetingToStart(Context context, long chatId, PasscodeMana */ public static void openMeetingRinging(Context context, long chatId, PasscodeManagement passcodeManagement) { logDebug("Open incoming call screen. Chat id is " + chatId); - passcodeManagement.setShowPasscodeScreen(false); + passcodeManagement.setShowPasscodeScreen(true); MegaApplication.getInstance().openCallService(chatId); Intent meetingIntent = new Intent(context, MeetingActivity.class); meetingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -152,7 +154,7 @@ public static void openMeetingRinging(Context context, long chatId, PasscodeMana */ public static void openMeetingInProgress(Context context, long chatId, boolean isNewTask, PasscodeManagement passcodeManagement) { logDebug("Open in progress call screen. Chat id is " + chatId); - passcodeManagement.setShowPasscodeScreen(false); + passcodeManagement.setShowPasscodeScreen(true); if (isNewTask) { MegaApplication.getInstance().openCallService(chatId); } @@ -160,6 +162,7 @@ public static void openMeetingInProgress(Context context, long chatId, boolean i Intent meetingIntent = new Intent(context, MeetingActivity.class); meetingIntent.setAction(MEETING_ACTION_IN); meetingIntent.putExtra(MEETING_CHAT_ID, chatId); + meetingIntent.putExtra(MEETING_IS_GUEST, MegaApplication.getInstance().getMegaApi().isEphemeralPlusPlus()); if (isNewTask) { logDebug("New task"); meetingIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -180,7 +183,7 @@ public static void openMeetingInProgress(Context context, long chatId, boolean i */ public static void openMeetingWithAudioOrVideo(Context context, long chatId, boolean isAudioEnable, boolean isVideoEnable, PasscodeManagement passcodeManagement) { logDebug("Open call with audio or video. Chat id is " + chatId); - passcodeManagement.setShowPasscodeScreen(false); + passcodeManagement.setShowPasscodeScreen(true); MegaApplication.getInstance().openCallService(chatId); Intent meetingIntent = new Intent(context, MeetingActivity.class); meetingIntent.setAction(MEETING_ACTION_IN); @@ -202,7 +205,7 @@ public static void openMeetingWithAudioOrVideo(Context context, long chatId, boo */ public static void openMeetingGuestMode(Context context, String meetingName, long chatId, String link, PasscodeManagement passcodeManagement) { logDebug("Open meeting in guest mode. Chat id is " + chatId); - passcodeManagement.setShowPasscodeScreen(false); + passcodeManagement.setShowPasscodeScreen(true); MegaApplication.getChatManagement().setOpeningMeetingLink(chatId, true); MegaApplication.getInstance().setIsLoggingRunning(true); @@ -224,10 +227,12 @@ public static void openMeetingGuestMode(Context context, String meetingName, lon */ public static boolean participatingInACall() { MegaChatApiAndroid megaChatApi = MegaApplication.getInstance().getMegaChatApi(); + MegaHandleList listCallsInitial = megaChatApi.getChatCalls(MegaChatCall.CALL_STATUS_INITIAL); MegaHandleList listCallsConnecting = megaChatApi.getChatCalls(MegaChatCall.CALL_STATUS_CONNECTING); MegaHandleList listCallsJoining = megaChatApi.getChatCalls(MegaChatCall.CALL_STATUS_JOINING); MegaHandleList listCallsInProgress = megaChatApi.getChatCalls(MegaChatCall.CALL_STATUS_IN_PROGRESS); - return listCallsConnecting.size() > 0 || listCallsJoining.size() > 0 || listCallsInProgress.size() > 0; + + return listCallsInitial.size() > 0 || listCallsConnecting.size() > 0 || listCallsJoining.size() > 0 || listCallsInProgress.size() > 0; } /** @@ -1069,6 +1074,7 @@ public static void startNewCall(Activity activity, SnackbarShower snackbarShower MegaChatPeerList peers = MegaChatPeerList.createInstance(); if (chat == null) { + logDebug("Chat doesn't exist"); ArrayList chats = new ArrayList<>(); ArrayList usersNoChat = new ArrayList<>(); usersNoChat.add(user); @@ -1080,8 +1086,10 @@ public static void startNewCall(Activity activity, SnackbarShower snackbarShower megaChatApi.createChat(false, peers, listener); } } else if (megaChatApi.getChatCall(chat.getChatId()) != null) { + logDebug("There is a call, open it"); openMeetingInProgress(activity, chat.getChatId(), true, passcodeManagement); } else if (isStatusConnected(activity, chat.getChatId())) { + logDebug("There is no call, start it"); MegaApplication.setUserWaitingForCall(user.getHandle()); startCallWithChatOnline(activity, chat); } @@ -1185,14 +1193,20 @@ public static boolean isChatConnectedInOrderToInitiateACall(int newState, MegaCh /** * Method to display a dialogue informing the user that he/she cannot start or join a meeting while on a call in progress. + * + * @param context Context of Activity + * @param message String with the text to show in the dialogue + * @param passcodeManagement To disable passcode. */ - public static void showConfirmationInACall(Context context) { - DialogInterface.OnClickListener dialogClickListener = (dialog, which) -> { - }; - + public static void showConfirmationInACall(Context context, String message, PasscodeManagement passcodeManagement) { MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context); - String message = context.getResources().getString(R.string.ongoing_call_content); - builder.setMessage(message).setPositiveButton(R.string.general_ok, dialogClickListener).show(); + builder.setMessage(message) + .setPositiveButton(R.string.general_ok, (dialog, which) -> { + if (context instanceof OpenLinkActivity) { + returnActiveCall(context, passcodeManagement); + } + }) + .show(); } /** @@ -1272,7 +1286,7 @@ public static void checkMeetingInProgress(Context context, LoadPreviewListener.O if (amIParticipatingInAnotherCall(chatId)) { logDebug("I am participating in another call"); - showConfirmationInACall(context); + showConfirmationInACall(context, StringResourcesUtils.getString(R.string.text_join_call), passcodeManagement); return; } @@ -1416,4 +1430,25 @@ public static RemoteViews collapsedAndExpandedIncomingCallNotification(Context c return views; } + + /** + * Method to control when an attempt is made to initiate a call from a contact option + * + * @param context The Activity context + * @param passcodeManagement To disable passcode. + * @return True, if the call can be started. False, otherwise. + */ + public static boolean canCallBeStartedFromContactOption(Activity context, PasscodeManagement passcodeManagement) { + if (MegaApplication.getInstance().getStorageState() == STORAGE_STATE_PAYWALL) { + showOverDiskQuotaPaywallWarning(); + return false; + } + + if (CallUtil.participatingInACall()) { + showConfirmationInACall(context, StringResourcesUtils.getString(R.string.ongoing_call_content), passcodeManagement); + return false; + } + + return checkPermissionsCall(context, INVALID_TYPE_PERMISSIONS); + } } diff --git a/app/src/main/java/mega/privacy/android/app/utils/ChatUtil.java b/app/src/main/java/mega/privacy/android/app/utils/ChatUtil.java index ac3f241695d..d423b9b92cc 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/ChatUtil.java +++ b/app/src/main/java/mega/privacy/android/app/utils/ChatUtil.java @@ -17,6 +17,7 @@ import android.content.res.Resources; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.widget.SwitchCompat; +import androidx.core.content.ContextCompat; import android.media.AudioAttributes; import android.media.AudioFocusRequest; @@ -571,6 +572,56 @@ public static void setContactStatus(int userStatus, ImageView contactStateIcon, } } + /** + * Sets the contact status icon and status text + * + * @param userStatus contact's status + * @param textViewContactIcon view in which the status icon has to be set + * @param contactStateText view in which the status text has to be set + * @param where The status icon image resource is different based on the place where it's placed. + */ + public static void setContactStatusParticipantList(int userStatus, final ImageView textViewContactIcon, TextView contactStateText, StatusIconLocation where) { + MegaApplication app = MegaApplication.getInstance(); + Context context = app.getApplicationContext(); + int statusImageResId = getIconResourceIdByLocation(context, userStatus, where); + + if (statusImageResId == 0) { + textViewContactIcon.setVisibility(View.GONE); + } else { + Drawable drawable = ContextCompat.getDrawable(MegaApplication.getInstance().getApplicationContext(), statusImageResId); + textViewContactIcon.setImageDrawable(drawable); + textViewContactIcon.setVisibility(View.VISIBLE); + } + + if (contactStateText == null) { + return; + } + + contactStateText.setVisibility(View.VISIBLE); + + switch (userStatus) { + case MegaChatApi.STATUS_ONLINE: + contactStateText.setText(context.getString(R.string.online_status)); + break; + + case MegaChatApi.STATUS_AWAY: + contactStateText.setText(context.getString(R.string.away_status)); + break; + + case MegaChatApi.STATUS_BUSY: + contactStateText.setText(context.getString(R.string.busy_status)); + break; + + case MegaChatApi.STATUS_OFFLINE: + contactStateText.setText(context.getString(R.string.offline_status)); + break; + + case MegaChatApi.STATUS_INVALID: + default: + contactStateText.setVisibility(View.GONE); + } + } + /** * Get status icon image resource id by display mode and where the icon is placed. * 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 1ddb77e21b6..20f832b87cb 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 @@ -274,7 +274,6 @@ public class Constants { public static final String BROADCAST_ACTION_INTENT_SSL_VERIFICATION_FAILED = "INTENT_SSL_VERIFICATION_FAILED"; public static final String BROADCAST_ACTION_INTENT_SIGNAL_PRESENCE = "INTENT_SIGNAL_PRESENCE"; public static final String BROADCAST_ACTION_INTENT_UPDATE_ORDER = "INTENT_UPDATE_ORDER"; - public static final String BROADCAST_ACTION_INTENT_UPDATE_VIEW = "INTENT_UPDATE_VIEW"; public static final String BROADCAST_ACTION_INTENT_VOICE_CLIP_DOWNLOADED = "INTENT_VOICE_CLIP_DOWNLOADED"; public static final String BROADCAST_ACTION_INTENT_BUSINESS_EXPIRED = "INTENT_BUSINESS_EXPIRED"; public static final String BROADCAST_ACTION_INTENT_CHAT_ARCHIVED = "INTENT_CHAT_ARCHIVED"; @@ -325,6 +324,7 @@ public class Constants { public static final String INTENT_EXTRA_KEY_CHAT = "chat"; public static final String INTENT_EXTRA_KEY_TOOL_BAR_TITLE = "aBtitle"; public static final String INTENT_EXTRA_IS_FROM_MEETING = "extra_is_from_meeting"; + public static final String INTENT_EXTRA_MEETING_PARTICIPANTS = "participants_in_a_meeting"; public static final int FILE_BROWSER_ADAPTER = 2000; public static final int CONTACT_FILE_ADAPTER = 2001; @@ -562,6 +562,7 @@ public class Constants { public static final int INVITE_CONTACT_TYPE = 5; public static final int SNACKBAR_IMCOMPATIBILITY_TYPE = 6; public static final int DISMISS_ACTION_SNACKBAR = 7; + public static final int OPEN_FILE_SNACKBAR_TYPE = 8; public static final int INFO_ANIMATION = 3000; public static final int QUICK_INFO_ANIMATION = 500; @@ -897,9 +898,9 @@ public class Constants { public static final String NEW_ORDER = "NEW_ORDER"; public static final String IS_CLOUD_ORDER = "IS_CLOUD_ORDER"; public static final int ORDER_CLOUD = 0; - public static final int ORDER_CONTACTS = 1; - public static final int ORDER_OTHERS = 2; - public static final int ORDER_CAMERA = 3; + public static final int ORDER_OTHERS = 1; + public static final int ORDER_CAMERA = 2; + public static final int ORDER_OFFLINE = 3; public final static float MAX_WIDTH_APPBAR_LAND = 400; public final static float MAX_WIDTH_APPBAR_PORT = 200; diff --git a/app/src/main/java/mega/privacy/android/app/utils/FileUtil.java b/app/src/main/java/mega/privacy/android/app/utils/FileUtil.java index c90b1f13e68..eb159f96a56 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/FileUtil.java +++ b/app/src/main/java/mega/privacy/android/app/utils/FileUtil.java @@ -392,6 +392,85 @@ private static String checkFileInStorage(Cursor cursor, String data) { return null; } + /** + * Checks if the tapped MegaNode exists in local. + * Note: for node tapped event, only query system database by size. + * + * @param node MegaNode to check. + * @return The path of the file if the local file exists, null otherwise. + */ + public static String getTappedNodeLocalFile(MegaNode node) { + if (node == null) { + logWarning("Node is null"); + return null; + } + + String data = MediaStore.Files.FileColumns.DATA; + final String[] projection = {data}; + // Only query file by size, then compare file name. Since modification time will changed(copy to SD card) + // Display name may be null in the database. + final String selection = MediaStore.Files.FileColumns.SIZE + " = ?"; + final String[] selectionArgs = {String.valueOf(node.getSize())}; + + Context context = MegaApplication.getInstance(); + + try { + Cursor cursor = context.getContentResolver().query( MediaStore.Files.getContentUri(VOLUME_EXTERNAL), projection, selection, selectionArgs, null); + + List candidates = getPotentialLocalPath(context, cursor, data, node.getName()); + + for (String path : candidates) { + if (isFileAvailable(new File(path))) { + return path; + } + } + } catch (SecurityException e) { + // Workaround: devices with system below Android 10 cannot execute the query without storage permission. + logError("Haven't granted the permission.", e); + return null; + } + + return null; + } + + /** + * Searches in the correspondent storage established if the file exists. + * If a file path is found both on internal storage and SD card, + * put the path on internal storage before that on SD card. + * + * @param context Context object. + * @param cursor Cursor which contains all the requirements to find the file + * @param columnName Column name in which search + * @param fileName Name of the searching node. + * @return A list of file path that may be the path of the searching file. + */ + private static List getPotentialLocalPath(Context context, Cursor cursor, String columnName, String fileName) { + List candidates = new ArrayList<>(); + List sdCandidates = new ArrayList<>(); + + while (cursor != null && cursor.moveToNext()) { + int dataColumn = cursor.getColumnIndexOrThrow(columnName); + String path = cursor.getString(dataColumn); + + // Check file name. + if (path.endsWith(fileName)) { + if(SDCardUtils.isLocalFolderOnSDCard(context, path)) { + sdCandidates.add(path); + } else { + candidates.add(path); + } + } + } + + candidates.addAll(sdCandidates); + + if(cursor != null) { + cursor.close(); + } + + return candidates; + } + /* * Check is file belongs to the app */ @@ -650,7 +729,8 @@ public static void shareFile(Context context, File file) { Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND); shareIntent.setType(MimeTypeList.typeForName(file.getName()).getType() + "/*"); shareIntent.putExtra(Intent.EXTRA_STREAM, getUriForFile(context, file)); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + // To avoid java.lang.SecurityException: Permission Denial + shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); context.startActivity(Intent.createChooser(shareIntent, getString(R.string.context_share))); } @@ -665,7 +745,8 @@ public static void shareUri(Context context, String name, Uri uri) { Intent shareIntent = new Intent(android.content.Intent.ACTION_SEND); shareIntent.setType(MimeTypeList.typeForName(name).getType() + "/*"); shareIntent.putExtra(Intent.EXTRA_STREAM, uri); - shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + // To avoid java.lang.SecurityException: Permission Denial + shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); context.startActivity(Intent.createChooser(shareIntent, context.getString(R.string.context_share))); } diff --git a/app/src/main/java/mega/privacy/android/app/utils/LogUtil.java b/app/src/main/java/mega/privacy/android/app/utils/LogUtil.java index f39cca93d98..b2aeb172aa2 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/LogUtil.java +++ b/app/src/main/java/mega/privacy/android/app/utils/LogUtil.java @@ -278,4 +278,22 @@ public static void initLoggerKarere() { MegaChatApiAndroid.setLogLevel(DEBUG || statusLoggerKarere ? MegaChatApiAndroid.LOG_LEVEL_MAX : MegaChatApiAndroid.LOG_LEVEL_ERROR); logInfo("Karere logger initialized"); } + + /** + * Checks if loggerSDK is initialized. + * + * @return True if loggerSDK is initialized, false otherwise. + */ + public static boolean isLoggerSDKInitialized() { + return loggerSDK != null; + } + + /** + * Checks if loggerKarere is initialized. + * + * @return True if loggerKarere is initialized, false otherwise. + */ + public static boolean isLoggerKarereInitialized() { + return loggerKarere != null; + } } diff --git a/app/src/main/java/mega/privacy/android/app/utils/MegaNodeUtil.kt b/app/src/main/java/mega/privacy/android/app/utils/MegaNodeUtil.kt index 322b6f2fe46..69aac4cc7b4 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/MegaNodeUtil.kt +++ b/app/src/main/java/mega/privacy/android/app/utils/MegaNodeUtil.kt @@ -53,6 +53,9 @@ import mega.privacy.android.app.utils.FileUtil.* import mega.privacy.android.app.utils.LogUtil.logDebug import mega.privacy.android.app.utils.LogUtil.logWarning import mega.privacy.android.app.utils.MegaApiUtils.isIntentAvailable +import mega.privacy.android.app.utils.MegaNodeUtil.launchActionView +import mega.privacy.android.app.utils.MegaNodeUtil.manageURLNode +import mega.privacy.android.app.utils.MegaNodeUtil.openZip import mega.privacy.android.app.utils.StringResourcesUtils.getQuantityString import mega.privacy.android.app.utils.StringResourcesUtils.getString import mega.privacy.android.app.utils.TextUtil.isTextEmpty @@ -1406,18 +1409,9 @@ object MegaNodeUtil { ) { val mime = MimeTypeList.typeForName(autoPlayInfo.nodeName) when { - mime.isZip -> { - val zipFile = File(autoPlayInfo.localPath) - - val intentZip = Intent(context, ZipBrowserActivityLollipop::class.java) - intentZip.putExtra( - ZipBrowserActivityLollipop.EXTRA_PATH_ZIP, zipFile.absolutePath - ) - intentZip.putExtra( - ZipBrowserActivityLollipop.EXTRA_HANDLE_ZIP, autoPlayInfo.nodeHandle - ) - - activityLauncher.launchActivity(intentZip) + // // ZIP file on SD card can't not be created by `new java.util.zip.ZipFile(path)`. + mime.isZip && !SDCardUtils.isLocalFolderOnSDCard(context, autoPlayInfo.localPath) -> { + openZip(context, activityLauncher, autoPlayInfo.localPath, autoPlayInfo.nodeHandle) } mime.isPdf -> { val pdfIntent = Intent(context, PdfViewerActivityLollipop::class.java) @@ -1478,32 +1472,97 @@ object MegaNodeUtil { if (isIntentAvailable(context, mediaIntent)) { activityLauncher.launchActivity(mediaIntent) } else { - sendFile(context, autoPlayInfo, activityLauncher, snackbarShower) + sendFile( + context, + autoPlayInfo.nodeName, + autoPlayInfo.localPath, + activityLauncher, + snackbarShower + ) } } } else -> { - try { - val viewIntent = Intent(Intent.ACTION_VIEW) + launchActionView( + context, + autoPlayInfo.nodeName, + autoPlayInfo.localPath, + activityLauncher, + snackbarShower + ) + } + } + } - if (!setLocalIntentParams( - context, autoPlayInfo.nodeName, viewIntent, - autoPlayInfo.localPath, false, snackbarShower - ) - ) { - return - } + /** + * Launch ZipBrowserActivityLollipop to preview a zip file. + * + * @param context Android context. + * @param activityLauncher interface to launch activity. + * @param zipFilePath The local path of the zip file. + * @param nodeHandle The handle of the corresponding node. + */ + @JvmStatic + fun openZip( + context: Context, + activityLauncher: ActivityLauncher, + zipFilePath: String, + nodeHandle: Long + ) { + val intentZip = Intent(context, ZipBrowserActivityLollipop::class.java) + intentZip.putExtra( + ZipBrowserActivityLollipop.EXTRA_PATH_ZIP, zipFilePath + ) + intentZip.putExtra( + ZipBrowserActivityLollipop.EXTRA_HANDLE_ZIP, nodeHandle + ) - viewIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION - if (isIntentAvailable(context, viewIntent)) { - activityLauncher.launchActivity(viewIntent) - } else { - sendFile(context, autoPlayInfo, activityLauncher, snackbarShower) - } - } catch (e: Exception) { - snackbarShower.showSnackbar(getString(R.string.general_already_downloaded)) - } + activityLauncher.launchActivity(intentZip) + } + + /** + * For the node that cannot be opened in-app. + * Launch an intent with ACTION_VIEW and let user choose to use which app to open it. + * + * @param context Android context + * @param nodeName Name of the node. + * @param localPath Local path of the node. + * @param activityLauncher interface to launch activity + * @param snackbarShower interface to show snackbar + */ + @JvmStatic + fun launchActionView( + context: Context, + nodeName: String, + localPath: String, + activityLauncher: ActivityLauncher, + snackbarShower: SnackbarShower + ) { + try { + val viewIntent = Intent(Intent.ACTION_VIEW) + + if (!setLocalIntentParams( + context, nodeName, viewIntent, + localPath, false, snackbarShower + ) + ) { + return + } + + viewIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION + if (isIntentAvailable(context, viewIntent)) { + activityLauncher.launchActivity(viewIntent) + } else { + sendFile( + context, + nodeName, + localPath, + activityLauncher, + snackbarShower + ) } + } catch (e: Exception) { + snackbarShower.showSnackbar(getString(R.string.general_already_downloaded)) } } @@ -1511,21 +1570,23 @@ object MegaNodeUtil { * Create an Intent with ACTION_SEND for an auto play file. * * @param context Android context - * @param autoPlayInfo auto play file info + * @param nodeName Name of the node. + * @param localPath Local path of the node. * @param activityLauncher interface to launch activity * @param snackbarShower interface to show snackbar */ private fun sendFile( context: Context, - autoPlayInfo: AutoPlayInfo, + nodeName: String, + localPath: String, activityLauncher: ActivityLauncher, snackbarShower: SnackbarShower ) { val intentShare = Intent(Intent.ACTION_SEND) if (!setLocalIntentParams( - context, autoPlayInfo.nodeName, intentShare, - autoPlayInfo.localPath, false, snackbarShower + context, nodeName, intentShare, + localPath, false, snackbarShower ) ) { return @@ -1701,4 +1762,52 @@ object MegaNodeUtil { } ) } + + /** + * Handle the event when a node is tapped. + * + * @param context Android context + * @param node The node tapped. + * @param nodeDownloader Function/Methd for downloading node. + * @param activityLauncher interface to launch activity + * @param snackbarShower interface to show snackbar + */ + @JvmStatic + fun onNodeTapped( + context: Context, + node: MegaNode, + nodeDownloader : (node: MegaNode) -> Unit, + activityLauncher: ActivityLauncher, + snackbarShower: SnackbarShower + ) { + val possibleLocalFile = getTappedNodeLocalFile(node) + + if (possibleLocalFile != null) { + logDebug("The node is already downloaded, found in local.") + + // ZIP file on SD card can't not be created by `new java.util.zip.ZipFile(path)`. + if (MimeTypeList.typeForName(node.name).isZip && !SDCardUtils.isLocalFolderOnSDCard( + context, + possibleLocalFile + ) + ) { + logDebug("The file is zip, open in-app.") + openZip( + context, + activityLauncher, possibleLocalFile, node.handle + ) + } else { + logDebug("The file cannot be opened in-app.") + launchActionView( + context, + node.name, + possibleLocalFile, + activityLauncher, + snackbarShower + ) + } + } else { + nodeDownloader(node) + } + } } diff --git a/app/src/main/java/mega/privacy/android/app/utils/PasscodeUtil.kt b/app/src/main/java/mega/privacy/android/app/utils/PasscodeUtil.kt index 820aa8160d5..132bc9a2bc3 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/PasscodeUtil.kt +++ b/app/src/main/java/mega/privacy/android/app/utils/PasscodeUtil.kt @@ -324,6 +324,13 @@ class PasscodeUtil @Inject constructor( passcodeManagement.lastPause = System.currentTimeMillis() } + /** + * Called when PasscodeLock activity is resumed to reset the lastPause value. + */ + fun resetLastPauseUpdate() { + passcodeManagement.lastPause = 0 + } + /** * Launches an intent to show passcode screen when the app is locked */ diff --git a/app/src/main/java/mega/privacy/android/app/utils/Util.java b/app/src/main/java/mega/privacy/android/app/utils/Util.java index f6155372869..56a73baf368 100644 --- a/app/src/main/java/mega/privacy/android/app/utils/Util.java +++ b/app/src/main/java/mega/privacy/android/app/utils/Util.java @@ -1229,7 +1229,7 @@ public static void showSnackbar(Context context, int snackbarType, String messag } } - private static View getRootViewFromContext(Context context) { + public static View getRootViewFromContext(Context context) { BaseActivity activity = (BaseActivity)context; View rootView = null; try { diff --git a/app/src/main/jni/mega/sdk b/app/src/main/jni/mega/sdk index 250d5fcd1b1..927d1437c8b 160000 --- a/app/src/main/jni/mega/sdk +++ b/app/src/main/jni/mega/sdk @@ -1 +1 @@ -Subproject commit 250d5fcd1b1231c89dfc9c0038b8836ca6d0b7c9 +Subproject commit 927d1437c8bb5e6f576604eda17a8b69cf9c3dd2 diff --git a/app/src/main/jni/megachat/sdk b/app/src/main/jni/megachat/sdk index 60e8b844256..18866819dbd 160000 --- a/app/src/main/jni/megachat/sdk +++ b/app/src/main/jni/megachat/sdk @@ -1 +1 @@ -Subproject commit 60e8b8442560e7fb22ba1015b32bd7c6be9600a7 +Subproject commit 18866819dbdcaf6c947586a874a6987c4d92f2db diff --git a/app/src/main/res/layout/activity_meeting.xml b/app/src/main/res/layout/activity_meeting.xml index 72fd2e8fd9b..bd1c4d6378c 100644 --- a/app/src/main/res/layout/activity_meeting.xml +++ b/app/src/main/res/layout/activity_meeting.xml @@ -1,6 +1,7 @@ diff --git a/app/src/main/res/layout/bottom_sheet_meeting_participant.xml b/app/src/main/res/layout/bottom_sheet_meeting_participant.xml index bade5773e2c..65a10b532ce 100644 --- a/app/src/main/res/layout/bottom_sheet_meeting_participant.xml +++ b/app/src/main/res/layout/bottom_sheet_meeting_participant.xml @@ -110,7 +110,7 @@ + android:show="@{viewModel.showDividerSendMessage()}" /> + android:show="@{viewModel.showDividerPingToSpeaker()}" /> + android:show="@{viewModel.showDividerMakeModerator()}" /> + android:orientation="vertical"> @@ -35,10 +35,12 @@ app:drawableStartCompat="@drawable/ic_sort_name_descending" tools:text="Name (descending)" /> - + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_marginStart="72dp" + android:background="@color/grey_012_white_012" /> - + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_marginStart="72dp" + android:background="@color/grey_012_white_012" /> - + android:layout_width="match_parent" + android:layout_height="1dp" + android:layout_marginStart="72dp" + android:background="@color/grey_012_white_012" /> + + + + - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_fileexplorerlist.xml b/app/src/main/res/layout/fragment_fileexplorerlist.xml index e337256cc57..047825bccb7 100644 --- a/app/src/main/res/layout/fragment_fileexplorerlist.xml +++ b/app/src/main/res/layout/fragment_fileexplorerlist.xml @@ -83,9 +83,7 @@ android:layout_centerHorizontal="true" android:layout_centerInParent="true" android:choiceMode="multipleChoice" - android:columnWidth ="172dp" - android:paddingRight="4dp" - android:paddingLeft="4dp"/> + android:columnWidth ="172dp" /> -