diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6e2831a5..b41bc1f2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+# Build 219 (1.2)
+2023-11-02
+
+- New troubleshooting activity
+- Improved group cloning: it now preserves group admins
+
+# ~~Build 218 (1.1.1)~~
+2023-10-28
+
+- Internal release
+
# Build 217 (1.1)
2023-10-24
diff --git a/obv_messenger/app/build.gradle b/obv_messenger/app/build.gradle
index e430c2d1..6b241e70 100644
--- a/obv_messenger/app/build.gradle
+++ b/obv_messenger/app/build.gradle
@@ -16,8 +16,8 @@ android {
applicationId "io.olvid.messenger"
minSdkVersion 21
targetSdk 34
- versionCode 217
- versionName "1.1"
+ versionCode 219
+ versionName "1.2"
vectorDrawables.useSupportLibrary true
multiDexEnabled true
resourceConfigurations += ['en', 'fr']
@@ -139,7 +139,8 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.6.2'
implementation 'androidx.compose.ui:ui-tooling-preview'
debugImplementation 'androidx.compose.ui:ui-tooling'
- implementation 'com.google.accompanist:accompanist-themeadapter-appcompat:0.31.1-alpha'
+ implementation 'com.google.accompanist:accompanist-themeadapter-appcompat:0.33.2-alpha'
+ implementation 'com.google.accompanist:accompanist-permissions:0.33.2-alpha'
implementation 'io.coil-kt:coil-compose:2.3.0'
// starting with zxing 3.4.0, API level 24 is required...
@@ -172,6 +173,7 @@ dependencies {
implementation 'androidx.sharetarget:sharetarget:1.2.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.work:work-runtime:2.8.1'
+ implementation 'androidx.datastore:datastore-preferences:1.0.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
implementation 'org.jsoup:jsoup:1.16.1'
diff --git a/obv_messenger/app/src/main/AndroidManifest.xml b/obv_messenger/app/src/main/AndroidManifest.xml
index 841a58fb..d1482e63 100644
--- a/obv_messenger/app/src/main/AndroidManifest.xml
+++ b/obv_messenger/app/src/main/AndroidManifest.xml
@@ -283,6 +283,9 @@
android:name=".onboarding.flow.OnboardingFlowActivity"
android:launchMode="singleTop"
android:theme="@style/AppTheme.NoActionBar.LightBlueStatusBar" />
+
preselectedGroupMembers) {
+ public static void openGroupCreationActivityForCloning(Context activityContext, String absolutePhotoUrl, String serializedGroupDetails, String serializedGroupType, List preselectedGroupMembers, List preselectedGroupAdminMembers) {
Intent intent = new Intent(getContext(), GroupCreationActivity.class);
if (absolutePhotoUrl != null) {
intent.putExtra(GroupCreationActivity.ABSOLUTE_PHOTO_URL_INTENT_EXTRA, absolutePhotoUrl);
@@ -345,6 +346,13 @@ public static void openGroupCreationActivityForCloning(Context activityContext,
}
intent.putParcelableArrayListExtra(GroupCreationActivity.PRESELECTED_GROUP_MEMBERS_INTENT_EXTRA, parcelables);
}
+ if (preselectedGroupAdminMembers != null) {
+ ArrayList parcelables = new ArrayList<>(preselectedGroupAdminMembers.size());
+ for (Contact contact : preselectedGroupAdminMembers) {
+ parcelables.add(new BytesKey(contact.bytesContactIdentity));
+ }
+ intent.putParcelableArrayListExtra(GroupCreationActivity.PRESELECTED_GROUP_ADMIN_MEMBERS_INTENT_EXTRA, parcelables);
+ }
activityContext.startActivity(intent);
}
@@ -1161,7 +1169,7 @@ public void run() {
private static EngineNotificationListener backupKeyListener = null;
- private void configureMdmWebDavAutomaticBackups() throws Exception {
+ static void configureMdmWebDavAutomaticBackups() throws Exception {
BackupCloudProviderService.CloudProviderConfiguration mdmAutoBackupConfiguration = MDMConfigurationSingleton.getAutoBackupConfiguration();
if (mdmAutoBackupConfiguration != null) {
BackupCloudProviderService.CloudProviderConfiguration currentAutoBackupConfiguration = SettingsActivity.getAutomaticBackupConfiguration();
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/AppSingleton.java b/obv_messenger/app/src/main/java/io/olvid/messenger/AppSingleton.java
index b59f1846..81e00c51 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/AppSingleton.java
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/AppSingleton.java
@@ -83,6 +83,7 @@
import io.olvid.messenger.notifications.AndroidNotificationManager;
import io.olvid.messenger.openid.KeycloakManager;
import io.olvid.messenger.services.BackupCloudProviderService;
+import io.olvid.messenger.services.MDMConfigurationSingleton;
import io.olvid.messenger.services.PeriodicTasksScheduler;
import io.olvid.messenger.settings.SettingsActivity;
@@ -135,14 +136,6 @@ private AppSingleton() {
SettingsActivity.setContactDisplayNameFormat(JsonIdentityDetails.FORMAT_STRING_FIRST_LAST_POSITION_COMPANY);
}
- if (lastBuildExecuted != 0 && lastBuildExecuted < 124) {
- // clear missing google service dialog hide preference
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(App.getContext());
- SharedPreferences.Editor editor = prefs.edit();
- editor.remove(SettingsActivity.USER_DIALOG_HIDE_GOOGLE_APIS);
- editor.apply();
- }
-
if (lastBuildExecuted != 0 && lastBuildExecuted < 136) {
// clear deprecated scaled_turn preference
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(App.getContext());
@@ -165,6 +158,19 @@ private AppSingleton() {
App.runThread(new ContactDisplayNameFormatChangedTask());
}
+ if (lastBuildExecuted != 0 && lastBuildExecuted < 219) {
+ // clear removed dialog hide preference
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(App.getContext());
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.remove("user_dialog_hide_battery_optimization");
+ editor.remove("user_dialog_hide_background_restricted");
+ editor.remove("user_dialog_hide_alarm_scheduling");
+ editor.remove("user_dialog_hide_allow_notifications");
+ editor.remove("user_dialog_hide_full_screen_notification");
+ editor.remove("pref_key_last_backup_reminder_timestamp");
+ editor.apply();
+ }
+
// TODO: enable this once location is no longer in beta
// if (lastBuildExecuted != 0 && lastBuildExecuted < 171) {
// // if the user has customized attach icon order, add the send location icon so they see it
@@ -649,6 +655,14 @@ public void generateIdentity(@NonNull final String server,
}
}
+ // in case we have an MDM configuration, reload it and reconfigure backups
+ MDMConfigurationSingleton.reloadMDMConfiguration();
+ try {
+ App.AppStartupTasks.configureMdmWebDavAutomaticBackups();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
App.runThread(new OwnedDevicesSynchronisationWithEngineTask(ownedIdentity.bytesOwnedIdentity));
selectIdentity(ownedIdentity.bytesOwnedIdentity, (OwnedIdentity ignored) -> {
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/databases/tasks/GroupCloningTasks.java b/obv_messenger/app/src/main/java/io/olvid/messenger/databases/tasks/GroupCloningTasks.java
index 5f561844..5782a624 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/databases/tasks/GroupCloningTasks.java
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/databases/tasks/GroupCloningTasks.java
@@ -123,6 +123,7 @@ public static ClonabilityOutput getClonability(@NonNull Group group) {
null,
absolutePhotoUrl,
clonableGroupContacts,
+ new ArrayList<>(),
nonGroupV2CapableContacts,
nonContactOrNonChanelSerializedDetails
);
@@ -144,6 +145,7 @@ public static ClonabilityOutput getClonability(@NonNull Group2 group2) {
}
List clonableGroupContacts = new ArrayList<>();
+ List clonableGroupAdminContacts = new ArrayList<>();
List nonContactOrNonChanelSerializedDetails = new ArrayList<>();
@@ -155,7 +157,11 @@ public static ClonabilityOutput getClonability(@NonNull Group2 group2) {
if (group2Member.contact == null || !group2Member.contact.capabilityGroupsV2 || group2Member.contact.establishedChannelCount == 0) {
nonContactOrNonChanelSerializedDetails.add(group2Member.identityDetails);
} else {
- clonableGroupContacts.add(group2Member.contact);
+ if (group2Member.permissionAdmin) {
+ clonableGroupAdminContacts.add(group2Member.contact);
+ } else {
+ clonableGroupContacts.add(group2Member.contact);
+ }
}
}
@@ -165,6 +171,7 @@ public static ClonabilityOutput getClonability(@NonNull Group2 group2) {
serializedGroupType,
absolutePhotoUrl,
clonableGroupContacts,
+ clonableGroupAdminContacts,
new ArrayList<>(),
nonContactOrNonChanelSerializedDetails
);
@@ -176,17 +183,19 @@ public static class ClonabilityOutput {
public final String serializedGroupDetails; // contains the actual group name (or custom name), never the members names
public final String serializedGroupType;
public final String absolutePhotoUrl;
- public final List clonableGroupContacts; // list of contacts in the group (or pending) with whom I have a channel and that have group v2 capability
+ public final List clonableGroupContacts; // list of non admin contacts in the group (or pending) with whom I have a channel and that have group v2 capability
+ public final List clonableGroupAdminContacts; // list of admins in the group (or pending) with whom I have a channel and that have group v2 capability
public final List nonGroupV2CapableContacts; // contacts without group v2 capability
public final List nonContactOrNonChanelSerializedDetails; // serializedIdentityDetails of group members without a channel or with whom I am not in contact
- public ClonabilityOutput(String groupDisplayName, String serializedGroupDetails, String serializedGroupType, String absolutePhotoUrl, List clonableGroupContacts, List nonGroupV2CapableContacts, List nonContactOrNonChanelSerializedDetails) {
+ public ClonabilityOutput(String groupDisplayName, String serializedGroupDetails, String serializedGroupType, String absolutePhotoUrl, List clonableGroupContacts, List clonableGroupAdminContacts, List nonGroupV2CapableContacts, List nonContactOrNonChanelSerializedDetails) {
this.groupDisplayName = groupDisplayName;
this.serializedGroupDetails = serializedGroupDetails;
this.serializedGroupType = serializedGroupType;
this.absolutePhotoUrl = absolutePhotoUrl;
this.clonableGroupContacts = clonableGroupContacts;
+ this.clonableGroupAdminContacts = clonableGroupAdminContacts;
this.nonGroupV2CapableContacts = nonGroupV2CapableContacts;
this.nonContactOrNonChanelSerializedDetails = nonContactOrNonChanelSerializedDetails;
}
@@ -228,11 +237,11 @@ public static void initiateGroupCloningOrWarnUser(FragmentActivity activity, Clo
.setMessage(activity.getString(R.string.dialog_message_clone_group_warning_missing_members,
clonabilityOutput.groupDisplayName,
sb.toString()))
- .setPositiveButton(R.string.button_label_proceed, ((DialogInterface dialog, int which) -> App.openGroupCreationActivityForCloning(activity, clonabilityOutput.absolutePhotoUrl, clonabilityOutput.serializedGroupDetails, clonabilityOutput.serializedGroupType, clonabilityOutput.clonableGroupContacts)))
+ .setPositiveButton(R.string.button_label_proceed, ((DialogInterface dialog, int which) -> App.openGroupCreationActivityForCloning(activity, clonabilityOutput.absolutePhotoUrl, clonabilityOutput.serializedGroupDetails, clonabilityOutput.serializedGroupType, clonabilityOutput.clonableGroupContacts, clonabilityOutput.clonableGroupAdminContacts)))
.setNegativeButton(R.string.button_label_cancel, null);
confirmationBuilder.create().show();
} else {
- App.openGroupCreationActivityForCloning(activity, clonabilityOutput.absolutePhotoUrl, clonabilityOutput.serializedGroupDetails, clonabilityOutput.serializedGroupType, clonabilityOutput.clonableGroupContacts);
+ App.openGroupCreationActivityForCloning(activity, clonabilityOutput.absolutePhotoUrl, clonabilityOutput.serializedGroupDetails, clonabilityOutput.serializedGroupType, clonabilityOutput.clonableGroupContacts, clonabilityOutput.clonableGroupAdminContacts);
}
}
}
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/databases/tasks/backup/RestoreAppDataFromBackupTask.java b/obv_messenger/app/src/main/java/io/olvid/messenger/databases/tasks/backup/RestoreAppDataFromBackupTask.java
index 9aa330f9..39d9708a 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/databases/tasks/backup/RestoreAppDataFromBackupTask.java
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/databases/tasks/backup/RestoreAppDataFromBackupTask.java
@@ -319,7 +319,6 @@ public Boolean call() {
appBackupPojo.settings.restore();
}
new Handler(Looper.getMainLooper()).post(SettingsActivity::setDefaultNightMode);
- SettingsActivity.setLastBackupReminderTimestamp(System.currentTimeMillis());
return true;
} catch (Exception e) {
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/discussion/settings/DiscussionSettingsActivity.java b/obv_messenger/app/src/main/java/io/olvid/messenger/discussion/settings/DiscussionSettingsActivity.java
index 85eee521..2f0f6efe 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/discussion/settings/DiscussionSettingsActivity.java
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/discussion/settings/DiscussionSettingsActivity.java
@@ -164,9 +164,14 @@ public void onBackPressed() {
AlertDialog.Builder builder = new SecureAlertDialogBuilder(this, R.style.CustomAlertDialog)
.setTitle(R.string.dialog_title_shared_ephemeral_settings_modified)
.setMessage(R.string.dialog_message_shared_ephemeral_settings_modified)
- .setNegativeButton(R.string.button_label_discard, (dialog, which) -> discussionSettingsViewModel.discardEphemeralSettings())
- .setPositiveButton(R.string.button_label_update, (dialog, which) -> discussionSettingsViewModel.saveEphemeralSettingsAndNotifyPeers())
- .setOnDismissListener(dialog -> super.onBackPressed());
+ .setNegativeButton(R.string.button_label_discard, (dialog, which) -> {
+ discussionSettingsViewModel.discardEphemeralSettings();
+ super.onBackPressed();
+ })
+ .setPositiveButton(R.string.button_label_update, (dialog, which) -> {
+ discussionSettingsViewModel.saveEphemeralSettingsAndNotifyPeers();
+ super.onBackPressed();
+ });
builder.create().show();
} else {
super.onBackPressed();
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/gallery/GalleryActivity.java b/obv_messenger/app/src/main/java/io/olvid/messenger/gallery/GalleryActivity.java
index 45a76312..b1aec755 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/gallery/GalleryActivity.java
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/gallery/GalleryActivity.java
@@ -360,7 +360,11 @@ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float ve
&& e1.getY() > 100 // no fling if starting from top of screen (like to show status bar)
) {
finish();
- overridePendingTransition(R.anim.none, R.anim.dismiss_from_fling_down);
+ if (velocityY < 0) {
+ overridePendingTransition(R.anim.none, R.anim.dismiss_from_fling_up);
+ } else {
+ overridePendingTransition(R.anim.none, R.anim.dismiss_from_fling_down);
+ }
return true;
}
return false;
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/group/GroupCreationActivity.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/group/GroupCreationActivity.kt
index 7d7240e7..9b26592e 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/group/GroupCreationActivity.kt
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/group/GroupCreationActivity.kt
@@ -65,6 +65,7 @@ import io.olvid.messenger.databases.entity.OwnedIdentity
import io.olvid.messenger.databases.entity.jsons.JsonExpiration
import io.olvid.messenger.fragments.FilteredContactListFragment
import io.olvid.messenger.group.GroupTypeModel.CustomGroup
+import io.olvid.messenger.group.GroupTypeModel.PrivateGroup
import io.olvid.messenger.group.GroupTypeModel.SimpleGroup
import io.olvid.messenger.settings.SettingsActivity
@@ -122,9 +123,15 @@ class GroupCreationActivity : LockableActivity(), OnClickListener {
)
)
}
- if (intent.hasExtra(PRESELECTED_GROUP_MEMBERS_INTENT_EXTRA)) {
- val preselectedContactBytesKeys = intent.getParcelableArrayListExtra(PRESELECTED_GROUP_MEMBERS_INTENT_EXTRA)
- if (preselectedContactBytesKeys != null) {
+ val preselectedContactAdminBytesKeys = intent.getParcelableArrayListExtra(
+ PRESELECTED_GROUP_ADMIN_MEMBERS_INTENT_EXTRA
+ ) ?: arrayListOf()
+ val preselectedContactNonAdminBytesKeys =
+ intent.getParcelableArrayListExtra(PRESELECTED_GROUP_MEMBERS_INTENT_EXTRA)
+ ?: arrayListOf()
+ val preselectedContactBytesKeys = preselectedContactAdminBytesKeys + preselectedContactNonAdminBytesKeys
+ if (preselectedContactBytesKeys.isNotEmpty()) {
+ val admins : HashSet = hashSetOf()
App.runThread {
val preselectedContacts: MutableList = ArrayList()
for (bytesKey in preselectedContactBytesKeys) {
@@ -132,10 +139,14 @@ class GroupCreationActivity : LockableActivity(), OnClickListener {
.contactDao()[AppSingleton.getBytesCurrentIdentity(), bytesKey.bytes]
if (contact != null) {
preselectedContacts.add(contact)
+ if (preselectedContactAdminBytesKeys.contains(bytesKey)) {
+ admins.add(contact)
+ }
}
}
if (preselectedContacts.isNotEmpty()) {
runOnUiThread {
+ groupCreationViewModel.admins.value = admins
groupCreationViewModel.selectedContacts = preselectedContacts
contactsSelectionFragment?.setInitiallySelectedContacts(
preselectedContacts
@@ -143,7 +154,6 @@ class GroupCreationActivity : LockableActivity(), OnClickListener {
}
}
}
- }
}
}
setContentView(R.layout.activity_group_creation)
@@ -373,32 +383,46 @@ class GroupCreationActivity : LockableActivity(), OnClickListener {
val groupDescription = groupDetailsViewModel.groupDescription?.trim()
val jsonGroupDetails = JsonGroupDetails(groupName.trim(), groupDescription)
- val groupType = groupV2DetailsViewModel.getGroupTypeLiveData().value
+ val groupType = groupV2DetailsViewModel.getGroupTypeLiveData().value ?: SimpleGroup
val otherGroupMembers = HashMap>()
for (contact in selectedContacts) {
val permissions = groupV2DetailsViewModel.getPermissions(
- groupV2DetailsViewModel.getGroupTypeLiveData().value ?: SimpleGroup,
- groupCreationViewModel.admins.value?.find { it == contact } != null)
+ groupType,
+ if (groupType is PrivateGroup)
+ false
+ else
+ groupCreationViewModel.admins.value?.find { it == contact } != null)
otherGroupMembers[ObvBytesKey(contact.bytesContactIdentity)] = permissions
}
try {
val serializedGroupDetails =
AppSingleton.getJsonObjectMapper().writeValueAsString(jsonGroupDetails)
- val serializedGroupType = AppSingleton.getJsonObjectMapper().writeValueAsString((groupType ?: CustomGroup()).toJsonGroupType() )
+ val serializedGroupType = AppSingleton.getJsonObjectMapper()
+ .writeValueAsString((groupType).toJsonGroupType())
// set tmp ephemeral settings for just created group
AppSingleton.setCreatedGroupEphemeralSettings(
- JsonExpiration().takeIf { groupV2DetailsViewModel.getGroupTypeLiveData().value is CustomGroup }?.apply {
- readOnce = groupCreationViewModel.settingsReadOnce
- existenceDuration =
- groupCreationViewModel.settingsExistenceDuration
- visibilityDuration =
- groupCreationViewModel.settingsVisibilityDuration
- }
+ JsonExpiration().takeIf { groupV2DetailsViewModel.getGroupTypeLiveData().value is CustomGroup }
+ ?.apply {
+ readOnce = groupCreationViewModel.settingsReadOnce
+ existenceDuration =
+ groupCreationViewModel.settingsExistenceDuration
+ visibilityDuration =
+ groupCreationViewModel.settingsVisibilityDuration
+ }
)
val ownPermissions = Permission.DEFAULT_ADMIN_PERMISSIONS.toHashSet().apply {
- if (groupType is CustomGroup && groupType.remoteDeleteSetting == GroupTypeModel.RemoteDeleteSetting.NOBODY) this.remove(Permission.REMOTE_DELETE_ANYTHING)
+ if (groupType is CustomGroup && groupType.remoteDeleteSetting == GroupTypeModel.RemoteDeleteSetting.NOBODY) this.remove(
+ Permission.REMOTE_DELETE_ANYTHING
+ )
}
- AppSingleton.getEngine().startGroupV2CreationProtocol(serializedGroupDetails, groupAbsolutePhotoUrl, bytesOwnedIdentity, ownPermissions, otherGroupMembers, serializedGroupType)
+ AppSingleton.getEngine().startGroupV2CreationProtocol(
+ serializedGroupDetails,
+ groupAbsolutePhotoUrl,
+ bytesOwnedIdentity,
+ ownPermissions,
+ otherGroupMembers,
+ serializedGroupType
+ )
AppSingleton.getEngine().addNotificationListener(
EngineNotifications.GROUP_V2_CREATED_OR_UPDATED,
object : EngineNotificationListener {
@@ -670,5 +694,7 @@ class GroupCreationActivity : LockableActivity(), OnClickListener {
"serialized_group_type" // String with serialized JsonGroupType
const val PRESELECTED_GROUP_MEMBERS_INTENT_EXTRA =
"preselected_group_members" // Array of BytesKey
+ const val PRESELECTED_GROUP_ADMIN_MEMBERS_INTENT_EXTRA =
+ "preselected_group_admin_members" // Array of BytesKey
}
}
\ No newline at end of file
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/main/EmptyListCard.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/main/EmptyListCard.kt
deleted file mode 100644
index 2e2ca2b4..00000000
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/main/EmptyListCard.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Olvid for Android
- * Copyright © 2019-2023 Olvid SAS
- *
- * This file is part of Olvid for Android.
- *
- * Olvid is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License, version 3,
- * as published by the Free Software Foundation.
- *
- * Olvid is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with Olvid. If not, see .
- */
-
-package io.olvid.messenger.main
-
-import androidx.annotation.StringRes
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.shape.RoundedCornerShape
-import androidx.compose.material.Card
-import androidx.compose.material.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.colorResource
-import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.font.FontStyle
-import androidx.compose.ui.tooling.preview.Preview
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
-import io.olvid.messenger.R
-import io.olvid.messenger.R.color
-
-@Composable
-fun EmptyListCard(@StringRes stringRes: Int) {
- Card(
- modifier = Modifier
- .fillMaxWidth()
- .padding(8.dp),
- shape = RoundedCornerShape(4.dp),
- elevation = 4.dp
- ) {
- Column(
- modifier = Modifier
- .background(colorResource(id = color.almostWhite))
- .padding(8.dp)
- ) {
- Text(
- text = stringResource(
- id = stringRes
- ),
- fontSize = 16.sp,
- fontStyle = FontStyle.Italic,
- color = colorResource(
- id = color.greyTint
- )
- )
- }
- }
-}
-
-@Preview
-@Composable
-private fun EmptyListCardPreview() {
- AppCompatTheme {
- EmptyListCard(stringRes = R.string.explanation_empty_discussion_list)
- }
-}
\ No newline at end of file
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/main/MainActivity.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/main/MainActivity.kt
index 46bdc43f..3c12cbc1 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/main/MainActivity.kt
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/main/MainActivity.kt
@@ -18,12 +18,14 @@
*/
package io.olvid.messenger.main
+import android.Manifest
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build.VERSION
+import android.os.Build.VERSION_CODES
import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
import android.text.InputType
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
@@ -40,6 +42,21 @@ import androidx.activity.result.contract.ActivityResultContracts.RequestPermissi
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.material.SnackbarDuration
+import androidx.compose.material.SnackbarHost
+import androidx.compose.material.SnackbarHostState
+import androidx.compose.material.SnackbarResult.ActionPerformed
+import androidx.compose.material.SnackbarResult.Dismissed
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.unit.dp
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
@@ -49,26 +66,26 @@ import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentOnAttachListener
import androidx.lifecycle.Observer
+import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.switchMap
-import androidx.preference.PreferenceManager
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.widget.MarginPageTransformer
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
-import com.google.android.material.snackbar.BaseTransientBottomBar
-import com.google.android.material.snackbar.Snackbar
+import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
import io.olvid.engine.Logger
import io.olvid.engine.datatypes.ObvBase64
import io.olvid.engine.engine.types.EngineNotificationListener
import io.olvid.engine.engine.types.EngineNotifications
-import io.olvid.engine.engine.types.ObvBackupKeyInformation
import io.olvid.messenger.App
import io.olvid.messenger.AppSingleton
import io.olvid.messenger.BuildConfig
import io.olvid.messenger.R
+import io.olvid.messenger.R.color
+import io.olvid.messenger.R.dimen
+import io.olvid.messenger.R.id
import io.olvid.messenger.activities.ContactDetailsActivity
import io.olvid.messenger.activities.ObvLinkActivity
-import io.olvid.messenger.owneddetails.OwnedIdentityDetailsActivity
import io.olvid.messenger.activities.storage_manager.StorageManagerActivity
import io.olvid.messenger.billing.BillingUtils
import io.olvid.messenger.customClasses.ConfigurationPojo
@@ -83,7 +100,6 @@ import io.olvid.messenger.fragments.dialog.DiscussionSearchDialogFragment
import io.olvid.messenger.fragments.dialog.OtherKnownUsersDialogFragment
import io.olvid.messenger.fragments.dialog.OwnedIdentitySelectionDialogFragment
import io.olvid.messenger.fragments.dialog.OwnedIdentitySelectionDialogFragment.OnOwnedIdentitySelectedListener
-import io.olvid.messenger.google_services.GoogleServicesUtils
import io.olvid.messenger.main.calls.CallLogFragment
import io.olvid.messenger.main.contacts.ContactListFragment
import io.olvid.messenger.main.discussions.DiscussionListFragment
@@ -92,6 +108,7 @@ import io.olvid.messenger.notifications.AndroidNotificationManager
import io.olvid.messenger.onboarding.OnboardingActivity
import io.olvid.messenger.onboarding.flow.OnboardingFlowActivity
import io.olvid.messenger.openid.KeycloakManager
+import io.olvid.messenger.owneddetails.OwnedIdentityDetailsActivity
import io.olvid.messenger.plus_button.PlusButtonActivity
import io.olvid.messenger.services.UnifiedForegroundService
import io.olvid.messenger.settings.SettingsActivity
@@ -100,6 +117,10 @@ import io.olvid.messenger.settings.SettingsActivity.PingConnectivityIndicator.DO
import io.olvid.messenger.settings.SettingsActivity.PingConnectivityIndicator.FULL
import io.olvid.messenger.settings.SettingsActivity.PingConnectivityIndicator.LINE
import io.olvid.messenger.settings.SettingsActivity.PingConnectivityIndicator.NONE
+import io.olvid.messenger.troubleshooting.TroubleshootingActivity
+import io.olvid.messenger.troubleshooting.TroubleshootingDataStore
+import io.olvid.messenger.troubleshooting.shouldShowTroubleshootingSnackbar
+import kotlinx.coroutines.launch
import java.util.*
class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListener {
@@ -123,20 +144,11 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
@JvmField
var requestNotificationPermission = registerForActivityResult(
RequestPermission()
- ) { isGranted: Boolean ->
- if (!isGranted) {
- val prefs = PreferenceManager.getDefaultSharedPreferences(App.getContext())
- val hideDialog =
- prefs.getBoolean(SettingsActivity.USER_DIALOG_HIDE_ALLOW_NOTIFICATIONS, false)
- if (!hideDialog) {
- val builder = Utils.getNotificationDisabledDialogBuilder(this, prefs)
- builder.create().show()
- }
- }
- }
+ ) {}
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
+ lifecycleScope.launch { TroubleshootingDataStore(this@MainActivity).load() }
App.runThread {
val identityCount: Int = try {
AppSingleton.getEngine().ownedIdentities.size
@@ -145,14 +157,15 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
AppDatabase.getInstance().ownedIdentityDao().countAll()
}
if (identityCount == 0) {
- val onboardingIntent = Intent(applicationContext, OnboardingFlowActivity::class.java)
+ val onboardingIntent =
+ Intent(applicationContext, OnboardingFlowActivity::class.java)
startActivity(onboardingIntent)
finish()
}
}
try {
installSplashScreen()
- } catch (e: Exception) {
+ } catch (e: Exception) {
setTheme(R.style.AppTheme_NoActionBar)
}
@@ -163,6 +176,46 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
setSupportActionBar(toolbar)
val actionBar = supportActionBar
actionBar?.setDisplayShowTitleEnabled(false)
+ val snackBarContainer = findViewById(R.id.snackbar_container)
+ snackBarContainer.setContent {
+ val snackbarState = remember {
+ SnackbarHostState()
+ }
+ LaunchedEffect(Unit) {
+ if (savedInstanceState == null && shouldShowTroubleshootingSnackbar()) {
+ snackbarState.showSnackbar(
+ message = getString(R.string.snackbar_go_to_troubleshooting_message),
+ actionLabel = getString(R.string.snackbar_go_to_troubleshooting_button),
+ duration = SnackbarDuration.Long
+ ).let { snackbarResult ->
+ when (snackbarResult) {
+ Dismissed -> {}
+ ActionPerformed -> {
+ startActivity(
+ Intent(
+ this@MainActivity,
+ TroubleshootingActivity::class.java
+ )
+ )
+ }
+ }
+ }
+ }
+ }
+ AppCompatTheme {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ SnackbarHost(
+ hostState = snackbarState, modifier = Modifier
+ .widthIn(max = 400.dp)
+ .align(Alignment.BottomCenter)
+ .padding(vertical = 80.dp)
+ )
+ }
+ }
+ }
val gestureDetector = GestureDetector(this, object : SimpleOnGestureListener() {
var scrolled = false
override fun onDown(e: MotionEvent): Boolean {
@@ -175,7 +228,12 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
return true
}
- override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
+ override fun onScroll(
+ e1: MotionEvent?,
+ e2: MotionEvent,
+ distanceX: Float,
+ distanceY: Float
+ ): Boolean {
if (scrolled) {
return false
}
@@ -189,49 +247,50 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
event!!
)
}
- pingConnectivityDot = findViewById(R.id.ping_indicator_dot)
- pingConnectivityLine = findViewById(R.id.ping_indicator_line)
- pingConnectivityFull = findViewById(R.id.ping_indicator_full)
- pingConnectivityFullTextView = findViewById(R.id.ping_indicator_full_text_view)
- pingConnectivityFullPingTextView = findViewById(R.id.ping_indicator_full_ping_text_view)
- pingRed = ContextCompat.getColor(this, R.color.red)
- pingGolden = ContextCompat.getColor(this, R.color.golden)
- pingGreen = ContextCompat.getColor(this, R.color.green)
+ pingConnectivityDot = findViewById(id.ping_indicator_dot)
+ pingConnectivityLine = findViewById(id.ping_indicator_line)
+ pingConnectivityFull = findViewById(id.ping_indicator_full)
+ pingConnectivityFullTextView = findViewById(id.ping_indicator_full_text_view)
+ pingConnectivityFullPingTextView = findViewById(id.ping_indicator_full_ping_text_view)
+ pingRed = ContextCompat.getColor(this, color.red)
+ pingGolden = ContextCompat.getColor(this, color.golden)
+ pingGreen = ContextCompat.getColor(this, color.green)
AppSingleton.getWebsocketConnectivityStateLiveData().observe(this, pingListener)
tabsPagerAdapter = TabsPagerAdapter(
this,
- findViewById(R.id.tab_discussions_notification_dot),
- findViewById(R.id.tab_contacts_notification_dot),
- findViewById(R.id.tab_groups_notification_dot),
- findViewById(R.id.tab_calls_notification_dot)
+ findViewById(id.tab_discussions_notification_dot),
+ findViewById(id.tab_contacts_notification_dot),
+ findViewById(id.tab_groups_notification_dot),
+ findViewById(id.tab_calls_notification_dot)
)
tabImageViews = arrayOfNulls(4)
- tabImageViews[0] = findViewById(R.id.tab_discussions_button)
- tabImageViews[1] = findViewById(R.id.tab_contacts_button)
- tabImageViews[2] = findViewById(R.id.tab_groups_button)
- tabImageViews[3] = findViewById(R.id.tab_calls_button)
+ tabImageViews[0] = findViewById(id.tab_discussions_button)
+ tabImageViews[1] = findViewById(id.tab_contacts_button)
+ tabImageViews[2] = findViewById(id.tab_groups_button)
+ tabImageViews[3] = findViewById(id.tab_calls_button)
for (imageView in tabImageViews) {
imageView?.setOnClickListener(this)
}
mainActivityPageChangeListener = MainActivityPageChangeListener(tabImageViews)
viewPager.adapter = tabsPagerAdapter
viewPager.registerOnPageChangeCallback(mainActivityPageChangeListener!!)
- viewPager.setPageTransformer(MarginPageTransformer(resources.getDimensionPixelSize(R.dimen.main_activity_page_margin)))
+ viewPager.setPageTransformer(MarginPageTransformer(resources.getDimensionPixelSize(dimen.main_activity_page_margin)))
viewPager.offscreenPageLimit = 3
supportFragmentManager.addFragmentOnAttachListener(this)
- val addContactButton = findViewById(R.id.tab_plus_button)
+ val addContactButton = findViewById(id.tab_plus_button)
addContactButton.setOnClickListener(this)
- val focusHugger = findViewById(R.id.focus_hugger)
+ val focusHugger = findViewById(id.focus_hugger)
focusHugger.requestFocus()
// observe owned Identity (for initial view)
val ownedIdentityMutedImageView =
- findViewById(R.id.owned_identity_muted_marker_image_view)
+ findViewById(id.owned_identity_muted_marker_image_view)
AppSingleton.getCurrentIdentityLiveData().observe(this) { ownedIdentity: OwnedIdentity? ->
if (ownedIdentity == null) {
App.runThread {
if (AppDatabase.getInstance().ownedIdentityDao().countAll() == 0) {
- val onboardingIntent = Intent(applicationContext, OnboardingFlowActivity::class.java)
+ val onboardingIntent =
+ Intent(applicationContext, OnboardingFlowActivity::class.java)
startActivity(onboardingIntent)
finish()
}
@@ -266,7 +325,7 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
tabsPagerAdapter!!.hideNotificationDot(DISCUSSIONS_TAB)
}
}
- val unreadMarker = findViewById(R.id.owned_identity_unread_marker_image_view)
+ val unreadMarker = findViewById(id.owned_identity_unread_marker_image_view)
AppSingleton.getCurrentIdentityLiveData().switchMap { ownedIdentity: OwnedIdentity? ->
if (ownedIdentity == null) {
return@switchMap null
@@ -280,7 +339,6 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
unreadMarker.visibility = View.GONE
}
}
- showReminderSnackBarIfNeeded()
if (savedInstanceState != null && savedInstanceState.getBoolean(
ALREADY_CREATED_BUNDLE_EXTRA,
false
@@ -291,99 +349,15 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
UnifiedForegroundService.finishAndRemoveExtraTasks(this)
}
handleIntent(intent)
- }
- private fun showReminderSnackBarIfNeeded() {
- App.runThread {
- var snackBar: Snackbar? = null
- var dialogMessageResourceId = -1
- var dialogTitleResourceId = -1
- try {
- val lastReminderTimestamp = SettingsActivity.getLastBackupReminderTimestamp()
- if (AppDatabase.getInstance().contactDao().countAll() == 0L) {
- // no contacts --> do nothing
- return@runThread
- }
- val info: ObvBackupKeyInformation? = try {
- AppSingleton.getEngine().backupKeyInformation
- } catch (e: Exception) {
- // this will be retried the next time MainActivity is started
- Logger.e("Unable to retrieve backup info")
- return@runThread
- }
- if (info == null) {
- if (lastReminderTimestamp + 7 * 86400000L < System.currentTimeMillis()) {
- // no backup key generated
- snackBar = Snackbar.make(
- root!!,
- R.string.snackbar_message_setup_backup,
- BaseTransientBottomBar.LENGTH_INDEFINITE
- )
- dialogMessageResourceId = R.string.dialog_message_setup_backup_explanation
- dialogTitleResourceId = R.string.dialog_title_setup_backup_explanation
- }
- return@runThread
- }
- if (lastReminderTimestamp + 7 * 86400000L < System.currentTimeMillis() &&
- !SettingsActivity.useAutomaticBackup() && info.lastBackupExport + 7 * 86400000L < System.currentTimeMillis()
- ) {
- // no automatic backups, and no backups since more that a week
- snackBar = Snackbar.make(
- root!!,
- R.string.snackbar_message_remember_to_backup,
- BaseTransientBottomBar.LENGTH_INDEFINITE
- )
- dialogTitleResourceId = R.string.snackbar_message_remember_to_backup
- dialogMessageResourceId =
- if (BuildConfig.USE_GOOGLE_LIBS && GoogleServicesUtils.googleServicesAvailable(
- this
- )
- ) {
- R.string.dialog_message_remember_to_backup_explanation
- } else {
- R.string.dialog_message_remember_to_backup_explanation_no_google
- }
- return@runThread
- }
- if (lastReminderTimestamp + 7 * 86400000L < System.currentTimeMillis() && info.keyGenerationTimestamp + 14 * 86400000L < System.currentTimeMillis() && info.lastSuccessfulKeyVerificationTimestamp + 30 * 86400000L < System.currentTimeMillis()) {
- // all backup stuff is good, but key not verified since more than 30 days
- snackBar = Snackbar.make(
- root!!,
- R.string.snackbar_message_verify_backup_key,
- BaseTransientBottomBar.LENGTH_INDEFINITE
- )
- dialogTitleResourceId = R.string.snackbar_message_verify_backup_key
- dialogMessageResourceId = R.string.dialog_message_verify_backup_key_explanation
- return@runThread
- }
- } catch (e: Exception) {
- e.printStackTrace()
- } finally {
- if (snackBar != null) {
- val finalDialogMessageResourceId = dialogMessageResourceId
- val finalDialogTitleResourceId = dialogTitleResourceId
- val finalSnackBar: Snackbar = snackBar
- snackBar.setAction(R.string.button_label_show_me, OnClickListener {
- val builder = SecureAlertDialogBuilder(this, R.style.CustomAlertDialog)
- .setTitle(finalDialogTitleResourceId)
- .setMessage(finalDialogMessageResourceId)
- .setPositiveButton(R.string.button_label_backup_settings) { _, _ ->
- val intent = Intent(this, SettingsActivity::class.java)
- intent.putExtra(
- SettingsActivity.SUB_SETTING_PREF_KEY_TO_OPEN_INTENT_EXTRA,
- SettingsActivity.PREF_HEADER_KEY_BACKUP
- )
- startActivity(intent)
- }
- .setNegativeButton(R.string.button_label_remind_me_later) { _: DialogInterface?, _: Int ->
- SettingsActivity.setLastBackupReminderTimestamp(
- System.currentTimeMillis()
- )
- }
- builder.create().show()
- })
- Handler(Looper.getMainLooper()).post { finalSnackBar.show() }
- }
+ // check notifications permissions
+ if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
+ if (ContextCompat.checkSelfPermission(
+ this,
+ Manifest.permission.POST_NOTIFICATIONS
+ ) != PackageManager.PERMISSION_GRANTED
+ ) {
+ requestNotificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
}
@@ -499,6 +473,7 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
startActivity(forwardIntent)
}
}
+
LINK_ACTION -> {
// first detect the type of link to show an appropriate dialog
@@ -512,13 +487,14 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
ObvBase64.decode(configurationMatcher.group(2)),
ConfigurationPojo::class.java
)
- val dialogTitleResourceId: Int = if (configurationPojo.keycloak != null) {
- // offer to chose a profile or create a new one for profile binding
- R.string.dialog_title_chose_profile_keycloak_configuration
- } else {
- // offer to chose a profile or create a new one for licence activation or configuration
- R.string.dialog_title_chose_profile_configuration
- }
+ val dialogTitleResourceId: Int =
+ if (configurationPojo.keycloak != null) {
+ // offer to chose a profile or create a new one for profile binding
+ R.string.dialog_title_chose_profile_keycloak_configuration
+ } else {
+ // offer to chose a profile or create a new one for licence activation or configuration
+ R.string.dialog_title_chose_profile_configuration
+ }
val ownedIdentitySelectionDialogFragment =
OwnedIdentitySelectionDialogFragment.newInstance(
this,
@@ -540,8 +516,14 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
}
override fun onNewProfileCreationSelected() {
- val onboardingIntent = Intent(this@MainActivity, OnboardingActivity::class.java)
- .putExtra(PlusButtonActivity.LINK_URI_INTENT_EXTRA, uri)
+ val onboardingIntent = Intent(
+ this@MainActivity,
+ OnboardingActivity::class.java
+ )
+ .putExtra(
+ PlusButtonActivity.LINK_URI_INTENT_EXTRA,
+ uri
+ )
startActivity(onboardingIntent)
}
})
@@ -557,7 +539,8 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
e.printStackTrace()
}
} else if (ObvLinkActivity.INVITATION_PATTERN.matcher(uri).find()
- || ObvLinkActivity.MUTUAL_SCAN_PATTERN.matcher(uri).find()) {
+ || ObvLinkActivity.MUTUAL_SCAN_PATTERN.matcher(uri).find()
+ ) {
// offer to chose a profile
val ownedIdentitySelectionDialogFragment =
OwnedIdentitySelectionDialogFragment.newInstance(
@@ -580,7 +563,10 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
}
override fun onNewProfileCreationSelected() {
- val onboardingIntent = Intent(this@MainActivity, OnboardingActivity::class.java)
+ val onboardingIntent = Intent(
+ this@MainActivity,
+ OnboardingActivity::class.java
+ )
.putExtra(OnboardingActivity.LINK_URI_INTENT_EXTRA, uri)
startActivity(onboardingIntent)
}
@@ -602,14 +588,23 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
override fun onOwnedIdentitySelected(bytesOwnedIdentity: ByteArray) {
AppSingleton.getInstance()
.selectIdentity(bytesOwnedIdentity) {
- val plusIntent = Intent(this@MainActivity, PlusButtonActivity::class.java)
- .putExtra(PlusButtonActivity.LINK_URI_INTENT_EXTRA, uri)
+ val plusIntent = Intent(
+ this@MainActivity,
+ PlusButtonActivity::class.java
+ )
+ .putExtra(
+ PlusButtonActivity.LINK_URI_INTENT_EXTRA,
+ uri
+ )
startActivity(plusIntent)
}
}
override fun onNewProfileCreationSelected() {
- val onboardingIntent = Intent(this@MainActivity, OnboardingActivity::class.java)
+ val onboardingIntent = Intent(
+ this@MainActivity,
+ OnboardingActivity::class.java
+ )
.putExtra(OnboardingActivity.PROFILE_CREATION, true)
startActivity(onboardingIntent)
@@ -658,15 +653,19 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
contactListFragment = ContactListFragment()
contactListFragment!!
}
+
GROUPS_TAB -> {
GroupListFragment()
}
+
CALLS_TAB -> {
CallLogFragment()
}
+
DISCUSSIONS_TAB -> {
DiscussionListFragment()
}
+
else -> {
DiscussionListFragment()
}
@@ -704,16 +703,19 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
pingConnectivityLine!!.visibility = View.GONE
pingConnectivityFull!!.visibility = View.GONE
}
+
DOT -> {
pingConnectivityDot!!.visibility = View.VISIBLE
pingConnectivityLine!!.visibility = View.GONE
pingConnectivityFull!!.visibility = View.GONE
}
+
LINE -> {
pingConnectivityDot!!.visibility = View.GONE
pingConnectivityLine!!.visibility = View.VISIBLE
pingConnectivityFull!!.visibility = View.GONE
}
+
FULL -> {
pingConnectivityDot!!.visibility = View.GONE
pingConnectivityLine!!.visibility = View.GONE
@@ -723,13 +725,6 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
}
}
- override fun onWindowFocusChanged(hasFocus: Boolean) {
- super.onWindowFocusChanged(hasFocus)
- if (hasFocus && AppSingleton.getBytesCurrentIdentity() != null) {
- Utils.showDialogs(this)
- }
- }
-
override fun onPause() {
super.onPause()
Utils.stopPinging()
@@ -759,7 +754,7 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
}
override fun onBackPressed() {
- if (!moveTaskToBack(true)) {
+ if (!moveTaskToBack(true)) {
finishAndRemoveTask()
}
}
@@ -788,9 +783,11 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
})
}
}
+
DISCUSSIONS_TAB -> {
menuInflater.inflate(R.menu.menu_main_discussion_list, menu)
}
+
CALLS_TAB -> {
menuInflater.inflate(R.menu.menu_call_log, menu)
return true
@@ -808,6 +805,8 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
return true
} else if (itemId == R.id.action_storage_management) {
startActivity(Intent(this, StorageManagerActivity::class.java))
+ } else if (itemId == R.id.action_troubleshooting) {
+ startActivity(Intent(this, TroubleshootingActivity::class.java))
} else if (itemId == R.id.action_backup_settings) {
val intent = Intent(this, SettingsActivity::class.java)
intent.putExtra(
@@ -861,15 +860,19 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
R.id.tab_plus_button -> {
startActivity(Intent(this, PlusButtonActivity::class.java))
}
+
R.id.tab_discussions_button -> {
viewPager.currentItem = DISCUSSIONS_TAB
}
+
R.id.tab_contacts_button -> {
viewPager.currentItem = CONTACTS_TAB
}
+
R.id.tab_groups_button -> {
viewPager.currentItem = GROUPS_TAB
}
+
R.id.tab_calls_button -> {
viewPager.currentItem = CALLS_TAB
}
@@ -906,6 +909,7 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
1 -> pingConnectivityFullTextView!!.setText(
R.string.label_ping_connectivity_connecting
)
+
2 -> pingConnectivityFullTextView!!.setText(R.string.label_ping_connectivity_connected)
0 -> pingConnectivityFullTextView!!.setText(R.string.label_ping_connectivity_none)
else -> pingConnectivityFullTextView!!.setText(R.string.label_ping_connectivity_none)
@@ -915,9 +919,11 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
pingConnectivityFullPingTextView!!.text =
getString(R.string.label_over_max_ping_delay, 5)
}
+
0L -> {
pingConnectivityFullPingTextView!!.text = "-"
}
+
else -> {
pingConnectivityFullPingTextView!!.text =
getString(R.string.label_ping_delay, lastPing)
@@ -936,6 +942,7 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
lastPing = -1
runOnUiThread { refresh() }
}
+
EngineNotifications.PING_RECEIVED -> {
(userInfo[EngineNotifications.PING_RECEIVED_DELAY_KEY] as Long?)?.let {
lastPing = it
@@ -963,7 +970,8 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
private val imm: InputMethodManager =
getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
private val inactiveColor: Int = ContextCompat.getColor(this@MainActivity, R.color.greyTint)
- private val activeColor: Int = ContextCompat.getColor(this@MainActivity, R.color.olvid_gradient_light)
+ private val activeColor: Int =
+ ContextCompat.getColor(this@MainActivity, R.color.olvid_gradient_light)
private var currentPosition = -1
override fun onPageSelected(position: Int) {
@@ -996,6 +1004,7 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
color or ((positionOffset * (inactiveColor and 0xff0000) + (1 - positionOffset) * (activeColor and 0xff0000)).toInt() and 0xff0000)
imageViews[i]!!.setColorFilter(color)
}
+
position + 1 -> {
var color = -0x1000000
color =
@@ -1006,6 +1015,7 @@ class MainActivity : LockableActivity(), OnClickListener, FragmentOnAttachListen
color or ((positionOffset * (activeColor and 0xff0000) + (1 - positionOffset) * (inactiveColor and 0xff0000)).toInt() and 0xff0000)
imageViews[i]!!.setColorFilter(color)
}
+
else -> {
imageViews[i]!!.setColorFilter(inactiveColor)
}
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/main/MainScreenEmptyList.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/main/MainScreenEmptyList.kt
new file mode 100644
index 00000000..e21c4edc
--- /dev/null
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/main/MainScreenEmptyList.kt
@@ -0,0 +1,91 @@
+/*
+ * Olvid for Android
+ * Copyright © 2019-2023 Olvid SAS
+ *
+ * This file is part of Olvid for Android.
+ *
+ * Olvid is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * Olvid is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Olvid. If not, see .
+ */
+
+package io.olvid.messenger.main
+
+import androidx.annotation.DrawableRes
+import androidx.annotation.StringRes
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
+import io.olvid.messenger.R
+import io.olvid.messenger.R.color
+
+@Composable
+fun MainScreenEmptyList(@DrawableRes icon: Int, iconPadding: Dp = 0.dp, @StringRes title: Int, @StringRes subtitle: Int?) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 8.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Image(
+ modifier = Modifier.size(60.dp).padding(iconPadding).alpha(.8f),
+ painter = painterResource(id = icon),
+ contentDescription = "",
+ colorFilter = ColorFilter.tint(colorResource(id = R.color.greyTint))
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ Text(
+ text = stringResource(id = title),
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Bold,
+ textAlign = TextAlign.Center,
+ color = colorResource(id = color.almostBlack)
+ )
+ subtitle?.let {
+ Text(
+ text = stringResource(id = subtitle),
+ fontSize = 13.sp,
+ fontWeight = FontWeight.Normal,
+ textAlign = TextAlign.Center,
+ color = colorResource(id = color.greyTint)
+ )
+ }
+ Spacer(modifier = Modifier.height(64.dp))
+ }
+}
+
+@Preview
+@Composable
+private fun MainScreenEmptyListPreview() {
+ AppCompatTheme {
+ MainScreenEmptyList(icon = R.drawable.ic_phone_log,title = R.string.explanation_empty_discussion_list, subtitle = R.string.explanation_empty_discussion_list_sub)
+ }
+}
\ No newline at end of file
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/main/Utils.java b/obv_messenger/app/src/main/java/io/olvid/messenger/main/Utils.java
index d327f34b..3daf52a2 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/main/Utils.java
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/main/Utils.java
@@ -59,306 +59,20 @@
public class Utils {
public static boolean dialogsLoaded = false;
static boolean dialogShowing = false;
- private static final Deque dialogsToShow = new ArrayDeque<>();
- private static final String USER_DIALOG_ALLOW_NOTIFICATIONS = "allow_notifications";
- private static final String USER_DIALOG_GOOGLE_APIS = "google_apis";
- private static final String USER_DIALOG_BACKGROUND_RESTRICTED = "background_restricted";
- private static final String USER_DIALOG_BATTERY_OPTIMIZATION = "battery_optimization";
- private static final String USER_DIALOG_ALARM_SCHEDULING = "alarm_scheduling";
- private static final String USER_DIALOG_FULL_SCREEN_NOTIFICATION = "full_screen_notification";
-
- static void showDialogs(MainActivity activity) {
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(App.getContext());
-
- if (!dialogsLoaded) {
- dialogsLoaded = true;
-
- if ((!BuildConfig.USE_FIREBASE_LIB || !GoogleServicesUtils.googleServicesAvailable(activity))
- && !SettingsActivity.usePermanentWebSocket()) {
- boolean hideDialog = prefs.getBoolean(SettingsActivity.USER_DIALOG_HIDE_GOOGLE_APIS, false);
- if (!hideDialog) {
- dialogsToShow.offerLast(USER_DIALOG_GOOGLE_APIS);
- }
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- ActivityManager activityManager = (ActivityManager) activity.getSystemService(Context.ACTIVITY_SERVICE);
- if (activityManager != null && activityManager.isBackgroundRestricted()) {
- boolean hideDialog = prefs.getBoolean(SettingsActivity.USER_DIALOG_HIDE_BACKGROUND_RESTRICTED, false);
- if (!hideDialog) {
- dialogsToShow.offerLast(USER_DIALOG_BACKGROUND_RESTRICTED);
- }
- }
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- PowerManager pm = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
- if (pm != null && !pm.isIgnoringBatteryOptimizations(activity.getPackageName())) {
- boolean hideDialog = prefs.getBoolean(SettingsActivity.USER_DIALOG_HIDE_BATTERY_OPTIMIZATION, false);
- if (!hideDialog) {
- dialogsToShow.offerLast(USER_DIALOG_BATTERY_OPTIMIZATION);
- }
- }
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- AlarmManager alarmManager = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE);
- if (alarmManager != null && !alarmManager.canScheduleExactAlarms()) {
- boolean hideDialog = prefs.getBoolean(SettingsActivity.USER_DIALOG_HIDE_ALARM_SCHEDULING, false);
- if (!hideDialog) {
- dialogsToShow.offerLast(USER_DIALOG_ALARM_SCHEDULING);
- }
- }
- }
- NotificationManagerCompat notificationManager = NotificationManagerCompat.from(App.getContext());
- if (!notificationManager.areNotificationsEnabled()) {
- boolean hideDialog = prefs.getBoolean(SettingsActivity.USER_DIALOG_HIDE_ALLOW_NOTIFICATIONS, false);
- if (!hideDialog) {
- dialogsToShow.offerLast(USER_DIALOG_ALLOW_NOTIFICATIONS);
- }
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- NotificationManager notifManager = activity.getSystemService(NotificationManager.class);
- if (!notifManager.canUseFullScreenIntent()) {
- boolean hideDialog = prefs.getBoolean(SettingsActivity.USER_DIALOG_HIDE_FULL_SCREEN_NOTIFICATION, false);
- if (!hideDialog) {
- dialogsToShow.offerLast(USER_DIALOG_FULL_SCREEN_NOTIFICATION);
- }
- }
- }
- }
-
- if (dialogShowing) {
- return;
- }
-
- String dialogToShow = dialogsToShow.pollFirst();
- if (dialogToShow == null) {
- return;
- }
-
- dialogShowing = true;
- switch (dialogToShow) {
- case USER_DIALOG_GOOGLE_APIS: {
- View dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_view_message_and_checkbox, null);
- TextView message = dialogView.findViewById(R.id.dialog_message);
- message.setText(R.string.dialog_message_google_apis_missing);
- CheckBox checkBox = dialogView.findViewById(R.id.checkbox);
- checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(SettingsActivity.USER_DIALOG_HIDE_GOOGLE_APIS, isChecked);
- editor.apply();
- });
-
- AlertDialog.Builder builder = new SecureAlertDialogBuilder(activity, R.style.CustomAlertDialog);
- builder.setTitle(R.string.dialog_title_google_apis_missing)
- .setView(dialogView)
- .setNeutralButton(R.string.button_label_skip, null)
- .setPositiveButton(R.string.button_label_enable_permanent_websocket, (DialogInterface dialogInterface, int which) -> {
- SettingsActivity.setUsePermanentWebSocket(true);
- activity.startService(new Intent(activity, UnifiedForegroundService.class));
- })
- .setOnDismissListener((DialogInterface dialog) -> {
- dialogShowing = false;
- showDialogs(activity);
- });
- builder.create().show();
- break;
- }
- case USER_DIALOG_BACKGROUND_RESTRICTED: {
- View dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_view_message_and_checkbox, null);
- TextView message = dialogView.findViewById(R.id.dialog_message);
- message.setText(R.string.dialog_message_background_restricted);
- CheckBox checkBox = dialogView.findViewById(R.id.checkbox);
- checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(SettingsActivity.USER_DIALOG_HIDE_BACKGROUND_RESTRICTED, isChecked);
- editor.apply();
- });
-
- AlertDialog.Builder builder = new SecureAlertDialogBuilder(activity, R.style.CustomAlertDialog);
- builder.setTitle(R.string.dialog_title_background_restricted)
- .setView(dialogView)
- .setNeutralButton(R.string.button_label_skip, null)
- .setPositiveButton(R.string.button_label_app_settings, (DialogInterface dialog, int which) -> {
- Intent intent = new Intent();
- intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
- intent.setData(uri);
- activity.startActivity(intent);
- })
- .setOnDismissListener((DialogInterface dialog) -> {
- dialogShowing = false;
- showDialogs(activity);
- });
-
- builder.create().show();
- break;
- }
- case USER_DIALOG_BATTERY_OPTIMIZATION: {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- View dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_view_message_and_checkbox, null);
- TextView message = dialogView.findViewById(R.id.dialog_message);
- message.setText(R.string.dialog_message_battery_optimization);
- CheckBox checkBox = dialogView.findViewById(R.id.checkbox);
- checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(SettingsActivity.USER_DIALOG_HIDE_BATTERY_OPTIMIZATION, isChecked);
- editor.apply();
- });
-
- AlertDialog.Builder builder = new SecureAlertDialogBuilder(activity, R.style.CustomAlertDialog);
- builder.setTitle(R.string.dialog_title_battery_optimization)
- .setView(dialogView)
- .setNeutralButton(R.string.button_label_skip, null)
- .setPositiveButton(R.string.button_label_battery_optimization_settings, (DialogInterface dialog, int which) -> {
- Intent intent = new Intent();
- intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
- intent.setData(uri);
- activity.startActivity(intent);
- })
- .setOnDismissListener((DialogInterface dialog) -> {
- dialogShowing = false;
- showDialogs(activity);
- });
-
- builder.create().show();
- }
- break;
- }
- case USER_DIALOG_ALARM_SCHEDULING: {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- View dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_view_message_and_checkbox, null);
- TextView message = dialogView.findViewById(R.id.dialog_message);
- message.setText(R.string.dialog_message_alarm_scheduling_forbidden);
- CheckBox checkBox = dialogView.findViewById(R.id.checkbox);
- checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(SettingsActivity.USER_DIALOG_HIDE_ALARM_SCHEDULING, isChecked);
- editor.apply();
- });
-
- AlertDialog.Builder builder = new SecureAlertDialogBuilder(activity, R.style.CustomAlertDialog);
- builder.setTitle(R.string.dialog_title_alarm_scheduling_forbidden)
- .setView(dialogView)
- .setNeutralButton(R.string.button_label_skip, null)
- .setPositiveButton(R.string.button_label_app_settings, (DialogInterface dialog, int which) -> {
- Intent intent = new Intent();
- intent.setAction(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
- intent.setData(uri);
- activity.startActivity(intent);
- })
- .setOnDismissListener((DialogInterface dialog) -> {
- dialogShowing = false;
- showDialogs(activity);
- });
-
- builder.create().show();
- }
- break;
- }
- case USER_DIALOG_ALLOW_NOTIFICATIONS: {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
- if (activity.shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) {
- AlertDialog.Builder builder = new SecureAlertDialogBuilder(activity, R.style.CustomAlertDialog);
- builder.setTitle(R.string.dialog_title_alarm_scheduling_forbidden)
- .setTitle(R.string.dialog_title_get_notified)
- .setMessage(R.string.dialog_message_get_notified)
- .setPositiveButton(R.string.button_label_ok, (DialogInterface dialog, int which) -> activity.requestNotificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS))
- .setNeutralButton(R.string.button_label_skip, null)
- .setOnDismissListener((DialogInterface dialog) -> {
- dialogShowing = false;
- showDialogs(activity);
- });
- builder.create().show();
- } else {
- activity.requestNotificationPermission.launch(Manifest.permission.POST_NOTIFICATIONS);
- dialogShowing = false;
- showDialogs(activity);
- }
- } else {
- AlertDialog.Builder builder = getNotificationDisabledDialogBuilder(activity, prefs);
- builder.setOnDismissListener((DialogInterface dialog) -> {
- dialogShowing = false;
- showDialogs(activity);
- });
- builder.create().show();
- }
- break;
- }
- case USER_DIALOG_FULL_SCREEN_NOTIFICATION: {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- View dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_view_message_and_checkbox, null);
- TextView message = dialogView.findViewById(R.id.dialog_message);
- message.setText(R.string.dialog_message_full_screen_notification);
- CheckBox checkBox = dialogView.findViewById(R.id.checkbox);
- checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(SettingsActivity.USER_DIALOG_HIDE_FULL_SCREEN_NOTIFICATION, isChecked);
- editor.apply();
- });
-
- AlertDialog.Builder builder = new SecureAlertDialogBuilder(activity, R.style.CustomAlertDialog);
- builder.setTitle(R.string.dialog_title_full_screen_notification)
- .setView(dialogView)
- .setNeutralButton(R.string.button_label_skip, null)
- .setPositiveButton(R.string.button_label_app_settings, (DialogInterface dialog, int which) -> {
- Intent intent = new Intent();
- intent.setAction(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
- intent.setData(uri);
- activity.startActivity(intent);
- })
- .setOnDismissListener((DialogInterface dialog) -> {
- dialogShowing = false;
- showDialogs(activity);
- });
-
- builder.create().show();
- }
- break;
- }
+ public static String getUptime(Context context) {
+ int uptimeSeconds = (int) ((System.currentTimeMillis() - App.appStartTimestamp) / 1000);
+ final String uptime;
+ if (uptimeSeconds > 86400) {
+ uptime = context.getResources().getQuantityString(R.plurals.text_app_uptime_days, uptimeSeconds / 86400, uptimeSeconds / 86400, (uptimeSeconds % 86400) / 3600, (uptimeSeconds % 3600) / 60, uptimeSeconds % 60);
+ } else if (uptimeSeconds > 3600) {
+ uptime = context.getString(R.string.text_app_uptime_hours, uptimeSeconds / 3600, (uptimeSeconds % 3600) / 60, uptimeSeconds % 60);
+ } else {
+ uptime = context.getString(R.string.text_app_uptime, uptimeSeconds / 60, uptimeSeconds % 60);
}
+ return uptime;
}
- static AlertDialog.Builder getNotificationDisabledDialogBuilder(FragmentActivity activity, SharedPreferences prefs) {
- View dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_view_message_and_checkbox, null);
- TextView message = dialogView.findViewById(R.id.dialog_message);
- message.setText(R.string.dialog_message_notifications_disabled);
- CheckBox checkBox = dialogView.findViewById(R.id.checkbox);
- checkBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
- SharedPreferences.Editor editor = prefs.edit();
- editor.putBoolean(SettingsActivity.USER_DIALOG_HIDE_ALLOW_NOTIFICATIONS, isChecked);
- editor.apply();
- });
-
- AlertDialog.Builder builder = new SecureAlertDialogBuilder(activity, R.style.CustomAlertDialog);
- builder.setTitle(R.string.dialog_title_notifications_disabled)
- .setView(dialogView)
- .setNeutralButton(R.string.button_label_skip, null)
- .setPositiveButton(R.string.button_label_open_settings, (DialogInterface dialog, int which) -> {
- try {
- Intent intent = new Intent();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- intent.setAction(Settings.ACTION_APP_NOTIFICATION_SETTINGS);
- intent.putExtra(Settings.EXTRA_APP_PACKAGE, activity.getPackageName());
- } else {
- intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
- Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
- intent.setData(uri);
- }
- activity.startActivity(intent);
- } catch (Exception ignored) { }
- });
- return builder;
- }
-
-
-
// region Websocket latency ping
static Timer pingTimer = null;
static boolean doPing = false;
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/main/calls/CallLogScreen.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/main/calls/CallLogScreen.kt
index 5f860a52..6cef2ed0 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/main/calls/CallLogScreen.kt
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/main/calls/CallLogScreen.kt
@@ -44,7 +44,7 @@ import androidx.compose.ui.unit.dp
import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
import io.olvid.messenger.R
import io.olvid.messenger.databases.dao.CallLogItemDao.CallLogItemAndContacts
-import io.olvid.messenger.main.EmptyListCard
+import io.olvid.messenger.main.MainScreenEmptyList
@Composable
fun CallLogScreen(
@@ -99,7 +99,15 @@ fun CallLogScreen(
}
}
} else {
- EmptyListCard(stringRes = R.string.explanation_empty_call_log)
+ Box(modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center) {
+ MainScreenEmptyList(
+ icon = R.drawable.ic_phone_log,
+ iconPadding = 6.dp,
+ title = R.string.explanation_empty_call_log,
+ subtitle = R.string.explanation_empty_call_log_sub
+ )
+ }
}
}
FloatingActionButton(modifier = Modifier
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/main/contacts/ContactListItem.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/main/contacts/ContactListItem.kt
index 8333b930..49865254 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/main/contacts/ContactListItem.kt
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/main/contacts/ContactListItem.kt
@@ -140,6 +140,7 @@ fun ContactListItem(
text = body,
color = colorResource(id = R.color.greyTint),
fontSize = 12.sp,
+ lineHeight = 15.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/main/contacts/ContactListScreen.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/main/contacts/ContactListScreen.kt
index 22aec4f0..a47140a8 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/main/contacts/ContactListScreen.kt
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/main/contacts/ContactListScreen.kt
@@ -58,7 +58,8 @@ import io.olvid.messenger.R.color
import io.olvid.messenger.R.drawable
import io.olvid.messenger.R.string
import io.olvid.messenger.customClasses.StringUtils
-import io.olvid.messenger.main.EmptyListCard
+import io.olvid.messenger.databases.entity.Contact
+import io.olvid.messenger.main.MainScreenEmptyList
import io.olvid.messenger.main.RefreshingIndicator
import io.olvid.messenger.main.contacts.ContactListViewModel.ContactOrKeycloakDetails
import io.olvid.messenger.main.contacts.ContactListViewModel.ContactType.*
@@ -136,6 +137,8 @@ fun ContactListScreen(
contactListViewModel.filterPatterns
),
shouldAnimateChannel = contactOrKeycloakDetails.contact?.shouldShowChannelCreationSpinner() == true && contactOrKeycloakDetails.contact.active,
+ publishedDetails = contactOrKeycloakDetails.contact?.newPublishedDetails == Contact.PUBLISHED_DETAILS_NEW_SEEN || contactOrKeycloakDetails.contact?.newPublishedDetails == Contact.PUBLISHED_DETAILS_NEW_UNSEEN,
+ publishedDetailsNotification = contactOrKeycloakDetails.contact?.newPublishedDetails == Contact.PUBLISHED_DETAILS_NEW_UNSEEN,
onClick = { onClick(contactOrKeycloakDetails) },
initialViewSetup = { initialView ->
when (contactOrKeycloakDetails.contactType) {
@@ -213,13 +216,21 @@ fun ContactListScreen(
Box(
modifier = Modifier
.fillMaxSize()
- .verticalScroll(rememberScrollState())
+ .verticalScroll(rememberScrollState()),
+ contentAlignment = Center
) {
- EmptyListCard(
- stringRes = if (contactListViewModel.isFiltering())
- string.explanation_no_contact_match_filter
- else string.explanation_empty_contact_list
- )
+ if (contactListViewModel.isFiltering())
+ MainScreenEmptyList(
+ icon = drawable.ic_contacts_filter,
+ title = string.explanation_no_contact_match_filter,
+ subtitle = null
+ )
+ else
+ MainScreenEmptyList(
+ icon = drawable.tab_contacts,
+ title = string.explanation_empty_contact_list,
+ subtitle = string.explanation_empty_contact_list_sub
+ )
}
}
}
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/main/discussions/DiscussionListItem.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/main/discussions/DiscussionListItem.kt
index 247ad6c6..cb11779c 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/main/discussions/DiscussionListItem.kt
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/main/discussions/DiscussionListItem.kt
@@ -176,6 +176,7 @@ fun DiscussionListItem(
text = title,
color = colorResource(id = R.color.primary700),
fontSize = 16.sp,
+ lineHeight = 20.sp,
fontWeight = FontWeight.Medium,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
@@ -185,6 +186,7 @@ fun DiscussionListItem(
text = body,
color = colorResource(id = R.color.greyTint),
fontSize = 14.sp,
+ lineHeight = 18.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
@@ -194,6 +196,7 @@ fun DiscussionListItem(
text = date,
color = colorResource(id = R.color.grey),
fontSize = 12.sp,
+ lineHeight = 15.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
@@ -236,6 +239,7 @@ fun DiscussionListItem(
.padding(horizontal = 7.dp, vertical = 2.dp),
text = "$unreadCount",
fontSize = 14.sp,
+ lineHeight = 17.sp,
color = colorResource(id = R.color.alwaysWhite)
)
}
@@ -257,8 +261,9 @@ fun DiscussionListItem(
attachmentCount,
attachmentCount
),
- Modifier.padding(horizontal = 4.dp, vertical = 2.dp),
+ modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp),
fontSize = 10.sp,
+ lineHeight = 13.sp,
fontWeight = FontWeight.Medium,
color = colorResource(id = R.color.grey)
)
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/main/discussions/DiscussionListScreen.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/main/discussions/DiscussionListScreen.kt
index a9072da3..1e98dd24 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/main/discussions/DiscussionListScreen.kt
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/main/discussions/DiscussionListScreen.kt
@@ -54,7 +54,7 @@ import io.olvid.messenger.R
import io.olvid.messenger.R.string
import io.olvid.messenger.customClasses.ifNull
import io.olvid.messenger.databases.entity.Discussion
-import io.olvid.messenger.main.EmptyListCard
+import io.olvid.messenger.main.MainScreenEmptyList
import io.olvid.messenger.main.RefreshingIndicator
import io.olvid.messenger.main.invitations.InvitationListViewModel
import io.olvid.messenger.main.invitations.getAnnotatedDate
@@ -197,8 +197,15 @@ fun DiscussionListScreen(
} else {
Box(modifier = Modifier
.fillMaxSize()
- .verticalScroll(rememberScrollState())) {
- EmptyListCard(stringRes = string.explanation_empty_discussion_list)
+ .verticalScroll(rememberScrollState()),
+ contentAlignment = Alignment.Center
+ ) {
+ MainScreenEmptyList(
+ icon = R.drawable.tab_discussions,
+ iconPadding = 4.dp,
+ title = R.string.explanation_empty_discussion_list,
+ subtitle = R.string.explanation_empty_discussion_list_sub
+ )
}
}
}
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/main/groups/GroupListItem.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/main/groups/GroupListItem.kt
index 39d1f9b4..b3ea1370 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/main/groups/GroupListItem.kt
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/main/groups/GroupListItem.kt
@@ -130,6 +130,7 @@ fun GroupListItem(
text = body,
color = colorResource(id = R.color.greyTint),
fontSize = 12.sp,
+ lineHeight = 15.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/main/groups/GroupListScreen.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/main/groups/GroupListScreen.kt
index f42473fd..d4bfc5be 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/main/groups/GroupListScreen.kt
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/main/groups/GroupListScreen.kt
@@ -24,8 +24,10 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredHeight
import androidx.compose.foundation.layout.requiredSize
@@ -54,11 +56,12 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
+import io.olvid.messenger.R
import io.olvid.messenger.R.color
import io.olvid.messenger.R.drawable
import io.olvid.messenger.R.string
import io.olvid.messenger.databases.dao.Group2Dao.GroupOrGroup2
-import io.olvid.messenger.main.EmptyListCard
+import io.olvid.messenger.main.MainScreenEmptyList
import io.olvid.messenger.main.RefreshingIndicator
@OptIn(ExperimentalMaterialApi::class)
@@ -119,13 +122,22 @@ fun GroupListScreen(
} else {
Box(
modifier = Modifier
- .fillMaxSize()
- .verticalScroll(rememberScrollState())
+ .fillMaxSize(),
+ contentAlignment = Alignment.TopCenter
) {
- Column(modifier = Modifier.fillMaxWidth()) {
- NewGroupButton(onNewGroupClick)
- EmptyListCard(stringRes = string.explanation_empty_group_list)
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState()),
+ contentAlignment = Alignment.Center
+ ) {
+ MainScreenEmptyList(
+ icon = R.drawable.tab_groups,
+ title = R.string.explanation_empty_group_list,
+ subtitle = R.string.explanation_empty_group_list_sub
+ )
}
+ NewGroupButton(onNewGroupClick)
}
}
}
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/onboarding/flow/animations/Shimmer.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/onboarding/flow/animations/Shimmer.kt
index 8963ecd0..dfd62f08 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/onboarding/flow/animations/Shimmer.kt
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/onboarding/flow/animations/Shimmer.kt
@@ -40,7 +40,6 @@ import androidx.compose.ui.unit.dp
import io.olvid.messenger.R
fun Modifier.shimmer(show:Boolean): Modifier = composed {
-
if (show) {
var size by remember {
mutableStateOf(IntSize.Zero)
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/onboarding/flow/screens/transfer/SourceSession.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/onboarding/flow/screens/transfer/SourceSession.kt
index f13cc079..627fa4fd 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/onboarding/flow/screens/transfer/SourceSession.kt
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/onboarding/flow/screens/transfer/SourceSession.kt
@@ -52,7 +52,6 @@ import io.olvid.messenger.onboarding.flow.OnboardingRoutes
import io.olvid.messenger.onboarding.flow.OnboardingScreen
import io.olvid.messenger.onboarding.flow.OnboardingStep
-@OptIn(ExperimentalComposeUiApi::class)
fun NavGraphBuilder.sourceSession(
onboardingFlowViewModel: OnboardingFlowViewModel,
onSasValidated: () -> Unit,
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/openid/KeycloakManager.java b/obv_messenger/app/src/main/java/io/olvid/messenger/openid/KeycloakManager.java
index f56d7fc7..66d2f441 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/openid/KeycloakManager.java
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/openid/KeycloakManager.java
@@ -33,6 +33,7 @@
import org.jose4j.lang.HashUtil;
import org.json.JSONException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
@@ -53,6 +54,7 @@
import io.olvid.messenger.AppSingleton;
import io.olvid.messenger.BuildConfig;
import io.olvid.messenger.customClasses.BytesKey;
+import io.olvid.messenger.customClasses.ConfigurationKeycloakPojo;
import io.olvid.messenger.notifications.AndroidNotificationManager;
import io.olvid.messenger.openid.jsons.KeycloakServerRevocationsAndStuff;
import io.olvid.messenger.openid.jsons.KeycloakUserDetailsAndStuff;
@@ -383,9 +385,8 @@ public void success(Boolean result) {
@Override
public void failed(int rfc) {
- authenticationRequiredOwnedIdentities.add(identityBytesKey);
- AndroidNotificationManager.displayKeycloakAuthenticationRequiredNotification(identityBytesKey.bytes);
- App.openAppDialogKeycloakAuthenticationRequired(kms.bytesOwnedIdentity, kms.clientId, kms.clientSecret, kms.serverUrl);
+ // in case of failure, we do nothing --> this is probably only a network error, and it will be tried again
+ // we do not want to prompt the user to authenticate in case of permanent connection error with the keycloak
}
});
}
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/plus_button/ConfigurationScannedFragment.java b/obv_messenger/app/src/main/java/io/olvid/messenger/plus_button/ConfigurationScannedFragment.java
index 15557fab..9126b035 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/plus_button/ConfigurationScannedFragment.java
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/plus_button/ConfigurationScannedFragment.java
@@ -580,7 +580,7 @@ public void callback(String notificationName, HashMap userInfo)
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.replace(R.id.new_license_status_placeholder, newSubscriptionStatusFragment);
transaction.commit();
- activateButton.setEnabled(true);
+ activateButton.setEnabled(apiKeyStatus != EngineAPI.ApiKeyStatus.LICENSES_EXHAUSTED);
});
break;
}
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/services/AlarmPermissionStateChangeReceiver.java b/obv_messenger/app/src/main/java/io/olvid/messenger/services/AlarmPermissionStateChangeReceiver.java
index 84d4197f..5cdb1dfa 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/services/AlarmPermissionStateChangeReceiver.java
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/services/AlarmPermissionStateChangeReceiver.java
@@ -43,12 +43,6 @@ public void onReceive(Context context, Intent intent) {
Logger.w("Permission to set exact alarms was granted!");
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (alarmManager != null && alarmManager.canScheduleExactAlarms()) {
- // reset hidden dialog flag
- final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- SharedPreferences.Editor editor = prefs.edit();
- editor.remove(SettingsActivity.USER_DIALOG_HIDE_ALARM_SCHEDULING);
- editor.apply();
-
// restart unified foreground service (in case app was still running)
context.startService(new Intent(context, UnifiedForegroundService.class));
UnifiedForegroundService.onAppBackground(context);
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/services/AvailableSpaceHelper.java b/obv_messenger/app/src/main/java/io/olvid/messenger/services/AvailableSpaceHelper.java
index 7a280d86..925c0ace 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/services/AvailableSpaceHelper.java
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/services/AvailableSpaceHelper.java
@@ -32,7 +32,7 @@
import io.olvid.messenger.settings.SettingsActivity;
public class AvailableSpaceHelper {
- private final static long AVAILABLE_SPACE_WARNING_THRESHOLD = 50_000_000L;
+ public final static long AVAILABLE_SPACE_WARNING_THRESHOLD = 50_000_000L;
private final static long MIN_REFRESH_INTERVAL_MILLIS = 600_000L; // 10 minutes
private final static long MIN_WARNING_INTERVAL_MILLIS = 7_200_000L; // 2 hours
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/services/MDMConfigurationSingleton.java b/obv_messenger/app/src/main/java/io/olvid/messenger/services/MDMConfigurationSingleton.java
index 05981e6a..4050a58f 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/services/MDMConfigurationSingleton.java
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/services/MDMConfigurationSingleton.java
@@ -24,21 +24,34 @@
import android.os.Bundle;
import android.util.Base64;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import java.net.URI;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import io.olvid.engine.datatypes.ObvBase64;
+import io.olvid.engine.engine.types.JsonIdentityDetails;
+import io.olvid.engine.engine.types.identities.ObvIdentity;
+import io.olvid.engine.engine.types.identities.ObvKeycloakState;
import io.olvid.messenger.App;
+import io.olvid.messenger.AppSingleton;
+import io.olvid.messenger.activities.ObvLinkActivity;
+import io.olvid.messenger.customClasses.ConfigurationPojo;
+import io.olvid.messenger.customClasses.StringUtils;
public class MDMConfigurationSingleton {
- public static final String KEYCLOAK_CONFIGURATION_URI = "keycloak_configuration_uri";
- public static final String DISABLE_NEW_VERSION_NOTIFICATION = "disable_new_version_notification";
- public static final String SETTINGS_CONFIGURATION_URI = "settings_configuration_uri";
- public static final String WEBDAV_AUTOMATIC_BACKUP_URI = "webdav_automatic_backup_uri";
- public static final String WEBDAV_AUTOMATIC_BACKUP_WRITE_ONLY = "webdav_automatic_backup_write_only";
- public static final String WEBDAV_KEY_ESCROW_PUBLIC_KEY = "webdav_key_escrow_public_key";
+ private static final String KEYCLOAK_CONFIGURATION_URI = "keycloak_configuration_uri";
+ private static final String DISABLE_NEW_VERSION_NOTIFICATION = "disable_new_version_notification";
+ private static final String SETTINGS_CONFIGURATION_URI = "settings_configuration_uri";
+ private static final String WEBDAV_AUTOMATIC_BACKUP_URI = "webdav_automatic_backup_uri";
+ private static final String WEBDAV_AUTOMATIC_BACKUP_WRITE_ONLY = "webdav_automatic_backup_write_only";
+ private static final String WEBDAV_KEY_ESCROW_PUBLIC_KEY = "webdav_key_escrow_public_key";
private static MDMConfigurationSingleton INSTANCE = null;
@@ -81,6 +94,11 @@ public MDMConfigurationSingleton() {
if (webdavAutomaticBackupUri != null) {
boolean writeOnly = restrictions.containsKey(WEBDAV_AUTOMATIC_BACKUP_WRITE_ONLY) && restrictions.getBoolean(WEBDAV_AUTOMATIC_BACKUP_WRITE_ONLY, false);
+ // if this identity is configured by keycloak, try to substitute variables
+ if (keycloakConfigurationUri != null) {
+ webdavAutomaticBackupUri = replaceWebDavUriVariablesFromKeycloakProfile(webdavAutomaticBackupUri, keycloakConfigurationUri);
+ }
+
try {
URI mdmUri = new URI(webdavAutomaticBackupUri);
String[] userInfo = (mdmUri.getUserInfo() == null) ? new String[0] : mdmUri.getUserInfo().split(":", 2);
@@ -136,11 +154,22 @@ public MDMConfigurationSingleton() {
public static MDMConfigurationSingleton getInstance() {
if (INSTANCE == null) {
- INSTANCE = new MDMConfigurationSingleton();
+ synchronized (MDMConfigurationSingleton.class) {
+ if (INSTANCE == null) {
+ INSTANCE = new MDMConfigurationSingleton();
+ }
+ }
}
return INSTANCE;
}
+ // called after a profile creation, in case the WEBDAV_AUTOMATIC_BACKUP_URI contains substitution parameters
+ public static void reloadMDMConfiguration() {
+ if (INSTANCE != null) {
+ INSTANCE = null;
+ }
+ }
+
public static String getKeycloakConfigurationUri() {
return getInstance().keycloakConfigurationUri;
}
@@ -163,4 +192,54 @@ public static String getWebdavKeyEscrowPublicKeyString() {
public static PublicKey getWebdavKeyEscrowPublicKey() {
return getInstance().webdavKeyEscrowPublicKey;
}
+
+
+ private String replaceWebDavUriVariablesFromKeycloakProfile(String uri, String keycloakConfigurationUri) {
+ try {
+ Matcher matcher = ObvLinkActivity.CONFIGURATION_PATTERN.matcher(keycloakConfigurationUri);
+ if (matcher.find()) {
+ ConfigurationPojo configurationPojo = AppSingleton.getJsonObjectMapper().readValue(ObvBase64.decode(matcher.group(2)), ConfigurationPojo.class);
+ if (configurationPojo.keycloak != null) {
+ JsonIdentityDetails details = null;
+
+ ObvIdentity[] ownedIdentities = AppSingleton.getEngine().getOwnedIdentities();
+ for (ObvIdentity ownedIdentity : ownedIdentities) {
+ if (ownedIdentity.isKeycloakManaged() && ownedIdentity.isActive()) {
+ ObvKeycloakState keycloakState = AppSingleton.getEngine().getOwnedIdentityKeycloakState(ownedIdentity.getBytesIdentity());
+ if (keycloakState != null && Objects.equals(keycloakState.keycloakServer, configurationPojo.keycloak.getServer())) {
+ if (details == null) {
+ details = ownedIdentity.getIdentityDetails();
+ } else {
+ throw new Exception("Multiple identities managed by Keycloak");
+ }
+ }
+ }
+ }
+
+ if (details != null) {
+ String first_name = unAccentAndClean(details.getFirstName());
+ String last_name = unAccentAndClean(details.getLastName());
+ String position = unAccentAndClean(details.getPosition());
+ String company = unAccentAndClean(details.getCompany());
+
+ return uri.replace("{{first_name}}", first_name).replace("{{last_name}}", last_name).replace("{{position}}", position).replace("{{company}}", company);
+ }
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ if (uri.contains("{{first_name}}") || uri.contains("{{last_name}}") || uri.contains("{{position}}") || uri.contains("{{company}}")) {
+ return null;
+ }
+ return uri;
+ }
+
+ @NonNull
+ private String unAccentAndClean(@Nullable String s) {
+ if (s == null) {
+ return "";
+ }
+ return StringUtils.unAccent(s).toLowerCase().trim().replaceAll("[^a-z]", "_");
+ }
}
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/settings/OtherPreferenceFragment.java b/obv_messenger/app/src/main/java/io/olvid/messenger/settings/OtherPreferenceFragment.java
index 0b0de71a..e869afb8 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/settings/OtherPreferenceFragment.java
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/settings/OtherPreferenceFragment.java
@@ -72,19 +72,12 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences();
if (sharedPreferences != null) {
SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.remove(SettingsActivity.USER_DIALOG_HIDE_GOOGLE_APIS);
- editor.remove(SettingsActivity.USER_DIALOG_HIDE_BACKGROUND_RESTRICTED);
- editor.remove(SettingsActivity.USER_DIALOG_HIDE_BATTERY_OPTIMIZATION);
- editor.remove(SettingsActivity.USER_DIALOG_HIDE_ALARM_SCHEDULING);
- editor.remove(SettingsActivity.USER_DIALOG_HIDE_ALLOW_NOTIFICATIONS);
- editor.remove(SettingsActivity.USER_DIALOG_HIDE_FULL_SCREEN_NOTIFICATION);
editor.remove(SettingsActivity.USER_DIALOG_HIDE_OPEN_EXTERNAL_APP);
editor.remove(SettingsActivity.USER_DIALOG_HIDE_FORWARD_MESSAGE_EXPLANATION);
editor.remove(SettingsActivity.USER_DIALOG_HIDE_OPEN_EXTERNAL_APP_LOCATION);
editor.remove(SettingsActivity.USER_DIALOG_HIDE_ADD_DEVICE_EXPLANATION);
editor.remove(SettingsActivity.PREF_KEY_FIRST_CALL_AUDIO_PERMISSION_REQUESTED);
editor.remove(SettingsActivity.PREF_KEY_FIRST_CALL_BLUETOOTH_PERMISSION_REQUESTED);
- editor.remove(SettingsActivity.PREF_KEY_LAST_BACKUP_REMINDER_TIMESTAMP);
editor.apply();
App.toast(R.string.toast_message_dialogs_restored, Toast.LENGTH_SHORT);
Utils.dialogsLoaded = false;
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/settings/SettingsActivity.java b/obv_messenger/app/src/main/java/io/olvid/messenger/settings/SettingsActivity.java
index 0b1e5e2a..75c2da13 100644
--- a/obv_messenger/app/src/main/java/io/olvid/messenger/settings/SettingsActivity.java
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/settings/SettingsActivity.java
@@ -78,6 +78,7 @@
import io.olvid.messenger.databases.AppDatabase;
import io.olvid.messenger.firebase.ObvFirebaseMessagingService;
import io.olvid.messenger.google_services.GoogleServicesUtils;
+import io.olvid.messenger.main.Utils;
import io.olvid.messenger.services.BackupCloudProviderService;
public class SettingsActivity extends LockableActivity implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
@@ -96,7 +97,6 @@ public class SettingsActivity extends LockableActivity implements PreferenceFrag
static final String PREF_KEY_LAST_AVAILABLE_SPACE_WARNING_TIMESTAMP = "pref_key_last_available_space_warning_timestamp";
static final String PREF_KEY_FIRST_CALL_AUDIO_PERMISSION_REQUESTED = "pref_key_first_call_audio_permission_requested";
static final String PREF_KEY_FIRST_CALL_BLUETOOTH_PERMISSION_REQUESTED = "pref_key_first_call_bluetooth_permission_requested";
- static final String PREF_KEY_LAST_BACKUP_REMINDER_TIMESTAMP = "pref_key_last_backup_reminder_timestamp";
static final String PREF_KEY_COMPOSE_MESSAGE_ICON_PREFERRED_ORDER = "pref_key_compose_message_icon_preferred_order";
static final String PREF_KEY_PREFERRED_REACTIONS = "pref_key_preferred_reactions";
@@ -326,13 +326,7 @@ public enum BlockUntrustedCertificate {
static final String PREF_KEY_NO_NOTIFY_CERTIFICATE_CHANGE_FOR_PREVIEWS = "pref_key_no_notify_certificate_change_for_previews";
static final boolean PREF_KEY_NO_NOTIFY_CERTIFICATE_CHANGE_FOR_PREVIEWS_DEFAULT = false;
-
- public static final String USER_DIALOG_HIDE_BATTERY_OPTIMIZATION = "user_dialog_hide_battery_optimization";
- public static final String USER_DIALOG_HIDE_BACKGROUND_RESTRICTED = "user_dialog_hide_background_restricted";
public static final String USER_DIALOG_HIDE_GOOGLE_APIS = "user_dialog_hide_google_apis";
- public static final String USER_DIALOG_HIDE_ALARM_SCHEDULING = "user_dialog_hide_alarm_scheduling";
- public static final String USER_DIALOG_HIDE_ALLOW_NOTIFICATIONS = "user_dialog_hide_allow_notifications";
- public static final String USER_DIALOG_HIDE_FULL_SCREEN_NOTIFICATION = "user_dialog_hide_full_screen_notification";
public static final String USER_DIALOG_HIDE_OPEN_EXTERNAL_APP = "user_dialog_hide_open_external_app";
public static final String USER_DIALOG_HIDE_FORWARD_MESSAGE_EXPLANATION = "user_dialog_hide_forward_message_explanation";
public static final String USER_DIALOG_HIDE_OPEN_EXTERNAL_APP_LOCATION = "user_dialog_hide_open_external_app_location";
@@ -533,14 +527,7 @@ public boolean onOptionsItemSelected(MenuItem item) {
extraFeatures.add("beta");
}
int uptimeSeconds = (int) ((System.currentTimeMillis() - App.appStartTimestamp) / 1000);
- final String uptime;
- if (uptimeSeconds > 86400) {
- uptime = getResources().getQuantityString(R.plurals.text_app_uptime_days, uptimeSeconds / 86400, uptimeSeconds / 86400, (uptimeSeconds % 86400) / 3600, (uptimeSeconds % 3600) / 60, uptimeSeconds % 60);
- } else if (uptimeSeconds > 3600) {
- uptime = getString(R.string.text_app_uptime_hours, uptimeSeconds / 3600, (uptimeSeconds % 3600) / 60, uptimeSeconds % 60);
- } else {
- uptime = getString(R.string.text_app_uptime, uptimeSeconds / 60, uptimeSeconds % 60);
- }
+ final String uptime = Utils.getUptime(this);
builder.setTitle(R.string.dialog_title_about_olvid)
.setPositiveButton(R.string.button_label_ok, null);
StringBuilder sb = new StringBuilder();
@@ -814,17 +801,6 @@ public static void setFirstCallBluetoothPermissionRequested(boolean requested) {
editor.apply();
}
- public static long getLastBackupReminderTimestamp() {
- return PreferenceManager.getDefaultSharedPreferences(App.getContext()).getLong(PREF_KEY_LAST_BACKUP_REMINDER_TIMESTAMP, 0);
- }
-
- public static void setLastBackupReminderTimestamp(long timestamp) {
- SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(App.getContext()).edit();
- editor.putLong(PREF_KEY_LAST_BACKUP_REMINDER_TIMESTAMP, timestamp);
- editor.apply();
- }
-
-
public static boolean useSystemEmojis() {
return PreferenceManager.getDefaultSharedPreferences(App.getContext()).getBoolean(PREF_KEY_USE_SYSTEM_EMOJIS, PREF_KEY_USE_SYSTEM_EMOJIS_DEFAULT);
}
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/CheckState.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/CheckState.kt
new file mode 100644
index 00000000..279c6151
--- /dev/null
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/CheckState.kt
@@ -0,0 +1,223 @@
+/*
+ * Olvid for Android
+ * Copyright © 2019-2023 Olvid SAS
+ *
+ * This file is part of Olvid for Android.
+ *
+ * Olvid is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * Olvid is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Olvid. If not, see .
+ */
+
+package io.olvid.messenger.troubleshooting
+
+import android.Manifest
+import android.app.ActivityManager
+import android.app.AlarmManager
+import android.app.NotificationManager
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build.VERSION
+import android.os.Build.VERSION_CODES
+import android.os.PowerManager
+import androidx.activity.ComponentActivity
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import io.olvid.engine.Logger
+import io.olvid.engine.engine.types.ObvBackupKeyInformation
+import io.olvid.messenger.AppSingleton
+import io.olvid.messenger.BuildConfig
+import io.olvid.messenger.R
+import io.olvid.messenger.google_services.GoogleServicesUtils
+import io.olvid.messenger.services.AvailableSpaceHelper
+import io.olvid.messenger.settings.SettingsActivity
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
+
+const val ALARM_CHECK_STATE = "alarm"
+const val BACKGROUND_CHECK_STATE = "background"
+const val BATTERY_CHECK_STATE = "battery"
+const val STORAGE_CHECK_STATE = "storage"
+const val SOCKET_CHECK_STATE = "socket"
+const val FULL_SCREEN_CHECK_STATE = "full_screen"
+const val BACKUP_CHECK_STATE = "backup"
+
+const val MUTE_KEY_PREFIX = "mute_"
+
+@Stable
+data class CheckState(val name: String, val troubleshootingDataStore: TroubleshootingDataStore, val statusIsOk: (K) -> Boolean = { (it is Boolean) && it }, val getStatus: () -> K) {
+ var status by mutableStateOf(getStatus())
+ var valid by mutableStateOf(statusIsOk(getStatus()))
+
+ fun refreshStatus() {
+ status = getStatus()
+ valid = statusIsOk(getStatus())
+ }
+
+ val isMute = troubleshootingDataStore.isMute("$MUTE_KEY_PREFIX$name")
+ suspend fun updateMute(mute: Boolean) {
+ troubleshootingDataStore.updateMute("$MUTE_KEY_PREFIX$name", mute)
+ }
+}
+
+@Composable
+internal fun LifecycleCheckerEffect(
+ checks: List>,
+ lifecycleEvent: Lifecycle.Event = Lifecycle.Event.ON_RESUME
+) {
+ val checkerObserver = remember(checks) {
+ LifecycleEventObserver { _, event ->
+ if (event == lifecycleEvent) {
+ for (check in checks) {
+ check.refreshStatus()
+ }
+ }
+ }
+ }
+ val lifecycle = LocalLifecycleOwner.current.lifecycle
+ DisposableEffect(lifecycle, checkerObserver) {
+ lifecycle.addObserver(checkerObserver)
+ onDispose { lifecycle.removeObserver(checkerObserver) }
+ }
+}
+
+
+fun ComponentActivity.getBatteryOptimizationsState() =
+ if (VERSION.SDK_INT >= VERSION_CODES.M) {
+ (getSystemService(ComponentActivity.POWER_SERVICE) as? PowerManager)?.isIgnoringBatteryOptimizations(
+ packageName
+ ) == true
+ } else {
+ true
+ }
+
+fun ComponentActivity.getAlarmState() =
+ if (VERSION.SDK_INT >= VERSION_CODES.S) {
+ (getSystemService(ComponentActivity.ALARM_SERVICE) as? AlarmManager)?.canScheduleExactAlarms() == true
+ } else {
+ true
+ }
+
+fun ComponentActivity.getBackgroundState() =
+ if (VERSION.SDK_INT >= VERSION_CODES.P) {
+ (getSystemService(ComponentActivity.ACTIVITY_SERVICE) as? ActivityManager)?.isBackgroundRestricted == false
+ } else {
+ true
+ }
+
+fun getStorageState() =
+ (AvailableSpaceHelper.getAvailableSpace()
+ ?: 0) > AvailableSpaceHelper.AVAILABLE_SPACE_WARNING_THRESHOLD
+
+fun Context.getPermanentSocketState() =
+ if (!BuildConfig.USE_FIREBASE_LIB || !GoogleServicesUtils.googleServicesAvailable(this)) {
+ SettingsActivity.usePermanentWebSocket()
+ } else {
+ true
+ }
+
+fun Context.getFullScreenIntentState() : Boolean {
+ if (VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ return getSystemService(
+ NotificationManager::class.java
+ ).canUseFullScreenIntent()
+ }
+ return true
+}
+
+fun getBackupState(): Int {
+ val info: ObvBackupKeyInformation? = try {
+ AppSingleton.getEngine().backupKeyInformation
+ } catch (e: Exception) {
+ // this will be retried the next time MainActivity is started
+ Logger.e("Unable to retrieve backup info")
+ return -1
+ }
+ if (info == null) {
+ // no backup key generated
+ return 2
+ } else {
+ if (!SettingsActivity.useAutomaticBackup() && info.lastBackupExport + 7 * 86400000L <= System.currentTimeMillis()) {
+ return 1
+ }
+ }
+ return 0
+}
+
+data class BackupStateInfo(val title: String, val description: String, val critical: Boolean)
+
+fun Context.getBackupStateInfo(): BackupStateInfo? {
+ var title: Int? = null
+ var description: Int = -1
+ var critical = false
+ try {
+ val info: ObvBackupKeyInformation? = try {
+ AppSingleton.getEngine().backupKeyInformation
+ } catch (e: Exception) {
+ // this will be retried the next time MainActivity is started
+ Logger.e("Unable to retrieve backup info")
+ return null
+ }
+ if (info == null) {
+ // no backup key generated
+ critical = true
+ title = R.string.snackbar_message_setup_backup
+ description = R.string.dialog_message_setup_backup_explanation
+ } else {
+ if (!SettingsActivity.useAutomaticBackup() && info.lastBackupExport + 7 * 86400000L < System.currentTimeMillis()
+ ) {
+ // no automatic backups, and no backups since more that a week
+ title = R.string.snackbar_message_remember_to_backup
+ description =
+ if (BuildConfig.USE_GOOGLE_LIBS && GoogleServicesUtils.googleServicesAvailable(this)) {
+ R.string.dialog_message_remember_to_backup_explanation
+ } else {
+ R.string.dialog_message_remember_to_backup_explanation_no_google
+ }
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+
+ return title?.let {
+ BackupStateInfo(getString(title), getString(description), critical)
+ }
+}
+
+fun ComponentActivity.shouldShowTroubleshootingSnackbar(): Boolean {
+ val troubleshootingDataStore = TroubleshootingDataStore(this)
+ return listOf(
+ CheckState(BATTERY_CHECK_STATE, troubleshootingDataStore) { getBatteryOptimizationsState() },
+ CheckState(ALARM_CHECK_STATE, troubleshootingDataStore) { getAlarmState() },
+ CheckState(BACKGROUND_CHECK_STATE, troubleshootingDataStore) { getBackgroundState() },
+ CheckState(STORAGE_CHECK_STATE, troubleshootingDataStore) { getStorageState() },
+ CheckState(SOCKET_CHECK_STATE, troubleshootingDataStore) { getPermanentSocketState() },
+ CheckState(FULL_SCREEN_CHECK_STATE, troubleshootingDataStore) { getFullScreenIntentState() },
+ CheckState(BACKUP_CHECK_STATE, troubleshootingDataStore) { getBackupState() == 0 },
+ ).any { checkState -> checkState.valid.not() && runBlocking { checkState.isMute.first() }.not() }
+ .or(
+ if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
+ ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED
+ } else {
+ false
+ }
+ )
+}
\ No newline at end of file
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/PingListener.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/PingListener.kt
new file mode 100644
index 00000000..5660fd5d
--- /dev/null
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/PingListener.kt
@@ -0,0 +1,79 @@
+/*
+ * Olvid for Android
+ * Copyright © 2019-2023 Olvid SAS
+ *
+ * This file is part of Olvid for Android.
+ *
+ * Olvid is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * Olvid is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Olvid. If not, see .
+ */
+
+package io.olvid.messenger.troubleshooting
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.remember
+import io.olvid.engine.engine.types.EngineNotificationListener
+import io.olvid.engine.engine.types.EngineNotifications
+import io.olvid.messenger.AppSingleton
+import io.olvid.messenger.main.Utils
+
+@Composable
+fun PingListener(connected: Boolean,pingCallback : (lastPing: Long) -> Unit) {
+
+ val pingListener = remember {
+ object : EngineNotificationListener {
+ var registrationNumber: Long = 0
+ override fun callback(notificationName: String, userInfo: HashMap) {
+ when (notificationName) {
+ EngineNotifications.PING_LOST -> {
+ pingCallback(-1)
+ }
+ EngineNotifications.PING_RECEIVED -> {
+ (userInfo[EngineNotifications.PING_RECEIVED_DELAY_KEY] as Long?)?.let {
+ pingCallback(it)
+ }
+ }
+ }
+ }
+
+ override fun setEngineNotificationListenerRegistrationNumber(registrationNumber: Long) {
+ this.registrationNumber = registrationNumber
+ }
+
+ override fun getEngineNotificationListenerRegistrationNumber(): Long {
+ return registrationNumber
+ }
+
+ override fun hasEngineNotificationListenerRegistrationNumber(): Boolean =
+ registrationNumber != 0L
+ }
+ }
+
+ DisposableEffect(connected, pingListener) {
+ if (connected) {
+ Utils.startPinging()
+ AppSingleton.getEngine()
+ .addNotificationListener(EngineNotifications.PING_LOST, pingListener)
+ AppSingleton.getEngine()
+ .addNotificationListener(EngineNotifications.PING_RECEIVED, pingListener)
+ }
+ onDispose {
+ pingCallback(-1)
+ Utils.stopPinging()
+ AppSingleton.getEngine()
+ .removeNotificationListener(EngineNotifications.PING_LOST, pingListener)
+ AppSingleton.getEngine()
+ .removeNotificationListener(EngineNotifications.PING_RECEIVED, pingListener)
+ }
+ }
+}
\ No newline at end of file
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/TroubleshootingActivity.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/TroubleshootingActivity.kt
new file mode 100644
index 00000000..91205d9a
--- /dev/null
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/TroubleshootingActivity.kt
@@ -0,0 +1,540 @@
+/*
+ * Olvid for Android
+ * Copyright © 2019-2023 Olvid SAS
+ *
+ * This file is part of Olvid for Android.
+ *
+ * Olvid is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * Olvid is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Olvid. If not, see .
+ */
+
+package io.olvid.messenger.troubleshooting
+
+import android.Manifest.permission
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import android.os.Build.VERSION
+import android.os.Build.VERSION_CODES
+import android.os.Bundle
+import android.os.storage.StorageManager
+import android.provider.Settings
+import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS
+import android.provider.Settings.ACTION_APP_NOTIFICATION_SETTINGS
+import android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
+import android.text.format.Formatter
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.compose.setContent
+import androidx.activity.result.contract.ActivityResultContracts.RequestPermission
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Text
+import androidx.compose.material.TextButton
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.Lifecycle.State.RESUMED
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.isGranted
+import com.google.accompanist.permissions.rememberPermissionState
+import com.google.accompanist.permissions.shouldShowRationale
+import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
+import io.olvid.messenger.AppSingleton
+import io.olvid.messenger.BuildConfig
+import io.olvid.messenger.R
+import io.olvid.messenger.R.string
+import io.olvid.messenger.customClasses.StringUtils
+import io.olvid.messenger.firebase.ObvFirebaseMessagingService
+import io.olvid.messenger.google_services.GoogleServicesUtils
+import io.olvid.messenger.services.AvailableSpaceHelper
+import io.olvid.messenger.services.UnifiedForegroundService
+import io.olvid.messenger.settings.SettingsActivity
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalPermissionsApi::class)
+class TroubleshootingActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContent {
+ val context = LocalContext.current
+ val coroutineScope = rememberCoroutineScope()
+ val troubleshootingDataStore = TroubleshootingDataStore(context)
+
+ val batteryOptimizationState = remember {
+ CheckState(BATTERY_CHECK_STATE, troubleshootingDataStore) { getBatteryOptimizationsState() }
+ }
+ val alarmState = remember {
+ CheckState(ALARM_CHECK_STATE, troubleshootingDataStore) { getAlarmState() }
+ }
+ val backgroundRestrictionState = remember {
+ CheckState(BACKGROUND_CHECK_STATE, troubleshootingDataStore) { getBackgroundState() }
+ }
+ val storageState = remember {
+ CheckState(STORAGE_CHECK_STATE, troubleshootingDataStore) { getStorageState() }
+ }
+ val permanentSocketState = remember {
+ CheckState(SOCKET_CHECK_STATE, troubleshootingDataStore) { getPermanentSocketState() }
+ }
+ val backupState = remember {
+ CheckState(BACKUP_CHECK_STATE, troubleshootingDataStore, statusIsOk = {a -> a == 0}) { getBackupState()}
+ }
+ val fullScreenIntentState = remember {
+ CheckState(FULL_SCREEN_CHECK_STATE, troubleshootingDataStore) { getFullScreenIntentState() }
+ }
+
+ val postNotificationsState = rememberPermissionState( permission.POST_NOTIFICATIONS )
+ val cameraState = rememberPermissionState(permission.CAMERA)
+ val microphoneState = rememberPermissionState(permission.RECORD_AUDIO)
+
+ LifecycleCheckerEffect(
+ checks = listOf>(
+ batteryOptimizationState,
+ alarmState,
+ backgroundRestrictionState,
+ storageState,
+ permanentSocketState,
+ backupState,
+ fullScreenIntentState
+ )
+ )
+
+ val troubleshootingItems : MutableState> = remember {
+ mutableStateOf(mutableListOf())
+ }
+
+ LaunchedEffect(Unit) {
+ val list : ArrayList> = ArrayList() // triple is (valid, critical, TroubleshootItemType)
+ if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
+ list.add(Triple(postNotificationsState.status.isGranted, true, TroubleshootingItemType.NOTIFICATIONS))
+ }
+
+ if (VERSION.SDK_INT >= VERSION_CODES.M) {
+ list.add(Triple(cameraState.status.isGranted, false, TroubleshootingItemType.CAMERA))
+ }
+
+ if (VERSION.SDK_INT >= VERSION_CODES.M) {
+ list.add(Triple(microphoneState.status.isGranted, false, TroubleshootingItemType.MICROPHONE))
+ }
+
+ if (VERSION.SDK_INT >= VERSION_CODES.M) {
+ list.add(Triple(batteryOptimizationState.valid, true, TroubleshootingItemType.BATTERY_OPTIMIZATION))
+ }
+
+ if (VERSION.SDK_INT >= VERSION_CODES.P) {
+ list.add(Triple(backgroundRestrictionState.valid, true, TroubleshootingItemType.BACKGROUND_RESTRICTION))
+ }
+
+ if (VERSION.SDK_INT >= VERSION_CODES.S) {
+ list.add(Triple(alarmState.valid, true, TroubleshootingItemType.ALARM))
+ }
+
+ if (VERSION.SDK_INT >= VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ list.add(Triple(fullScreenIntentState.valid, true, TroubleshootingItemType.FULL_SCREEN_INTENT))
+ }
+
+ if (!BuildConfig.USE_FIREBASE_LIB || !GoogleServicesUtils.googleServicesAvailable(this@TroubleshootingActivity)) {
+ list.add(Triple(permanentSocketState.valid, true, TroubleshootingItemType.PERMANENT_WEBSOCKET))
+ }
+
+ list.add(Triple(backupState.valid, true, TroubleshootingItemType.BACKUPS))
+
+ list.add(Triple(AppSingleton.getWebsocketConnectivityStateLiveData().value == 2, true, TroubleshootingItemType.CONNECTIVITY))
+
+ list.add(Triple(storageState.valid, true, TroubleshootingItemType.STORAGE))
+
+ list.sortBy {
+ when {
+ it.first -> 2
+ it.second.not() -> 1
+ else -> 0
+ }
+ }
+ troubleshootingItems.value = list.map {
+ it.third
+ }
+ }
+
+ AppCompatTheme {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(16.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+
+ FaqLinkHeader {
+ startActivity(
+ Intent(
+ Intent.ACTION_VIEW,
+ Uri.parse("https://olvid.io/faq/")
+ )
+ )
+ }
+
+ troubleshootingItems.value.forEach { troubleshootingItem ->
+ when(troubleshootingItem) {
+
+ TroubleshootingItemType.NOTIFICATIONS -> {
+ val notificationsPermissionLauncher = rememberLauncherForActivityResult(
+ contract = RequestPermission(),
+ onResult = { granted ->
+ if (VERSION.SDK_INT >= VERSION_CODES.M) {
+ if (granted.not() && postNotificationsState.status.shouldShowRationale.not() && shouldShowRequestPermissionRationale(
+ permission.POST_NOTIFICATIONS
+ ).not()
+ ) {
+ openAppNotificationSettings(context = context)
+ }
+ }
+ }
+ )
+ TroubleShootItem(
+ title = stringResource(id = string.troubleshooting_notifications_valid_title),
+ description = stringResource(
+ id = string.troubleshooting_notifications_valid_description
+ ) + if (BuildConfig.USE_FIREBASE_LIB && GoogleServicesUtils.googleServicesAvailable(
+ context
+ ) && !SettingsActivity.disablePushNotifications()
+ ) {
+ "\n\n" + stringResource(
+ string.dialog_message_about_last_push_notification,
+ if (ObvFirebaseMessagingService.getLastPushNotificationTimestamp() == null) {
+ "-"
+ } else {
+ StringUtils.getLongNiceDateString(
+ context,
+ ObvFirebaseMessagingService.getLastPushNotificationTimestamp()
+ )
+ }
+ )
+ } else "",
+ titleInvalid = stringResource(id = string.troubleshooting_notifications_invalid_title),
+ descriptionInvalid = stringResource(id = string.troubleshooting_notifications_invalid_description),
+ valid = postNotificationsState.status.isGranted
+ ) {
+ TextButton(
+ onClick =
+ {
+ notificationsPermissionLauncher.launch(permission.POST_NOTIFICATIONS)
+ }
+ ) {
+ Text(text = stringResource(id = string.troubleshooting_request_permission))
+ }
+ }
+ }
+
+ TroubleshootingItemType.CAMERA -> {
+ TroubleShootItem(
+ title = stringResource(id = string.troubleshooting_camera_valid_title),
+ description = stringResource(id = string.troubleshooting_camera_valid_description),
+ titleInvalid = stringResource(id = string.troubleshooting_camera_invalid_title),
+ valid = cameraState.status.isGranted,
+ critical = false,
+ ) {
+ TextButton(
+ onClick =
+ {
+ cameraState.launchPermissionRequest()
+ coroutineScope.launch {
+ delay(100)
+ if (lifecycle.currentState == RESUMED) {
+ startSettingsActivity()
+ }
+ }
+ }
+ ) {
+ Text(text = stringResource(id = string.troubleshooting_request_permission))
+ }
+ }
+ }
+
+ TroubleshootingItemType.MICROPHONE -> {
+ TroubleShootItem(
+ title = stringResource(id = string.troubleshooting_audio_recording_valid_title),
+ description = stringResource(id = string.troubleshooting_audio_recording_valid_description),
+ titleInvalid = stringResource(id = string.troubleshooting_audio_recording_invalid_title),
+ valid = microphoneState.status.isGranted,
+ critical = false,
+ ) {
+ TextButton(
+ onClick =
+ {
+ microphoneState.launchPermissionRequest()
+ coroutineScope.launch {
+ delay(100)
+ if (lifecycle.currentState == RESUMED) {
+ startSettingsActivity()
+ }
+ }
+ }
+ ) {
+ Text(text = stringResource(id = string.troubleshooting_request_permission))
+ }
+ }
+ }
+
+ TroubleshootingItemType.BATTERY_OPTIMIZATION -> {
+ TroubleShootItem(
+ title = stringResource(id = string.troubleshooting_battery_optimization_valid_title),
+ description = stringResource(id = string.troubleshooting_battery_optimization_valid_description),
+ descriptionInvalid = stringResource(id = string.troubleshooting_battery_optimization_invalid_description),
+ valid = batteryOptimizationState.valid,
+ checkState = batteryOptimizationState
+ ) {
+ TextButton(
+ onClick = {
+ startSettingsActivity(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
+ }
+ ) {
+ Text(text = stringResource(id = string.troubleshooting_request_permission))
+ }
+ }
+ }
+
+ TroubleshootingItemType.BACKGROUND_RESTRICTION -> {
+ TroubleShootItem(
+ title = stringResource(id = string.troubleshooting_background_restriction_valid_title),
+ description = stringResource(
+ id = string.troubleshooting_background_restriction_valid_description
+ ),
+ descriptionInvalid = stringResource(id = string.troubleshooting_background_restriction_invalid_description),
+ valid = backgroundRestrictionState.valid,
+ checkState = backgroundRestrictionState
+ ) {
+ TextButton(
+ onClick = {
+ startSettingsActivity()
+ }
+ ) {
+ Text(text = stringResource(id = string.button_label_app_settings))
+ }
+ }
+ }
+
+ TroubleshootingItemType.ALARM -> {
+ TroubleShootItem(
+ title = stringResource(id = string.troubleshooting_alarm_valid_title),
+ description = stringResource(
+ id = string.troubleshooting_alarm_valid_description
+ ),
+ descriptionInvalid = stringResource(id = string.troubleshooting_alarm_invalid_description),
+ valid = alarmState.valid,
+ checkState = alarmState
+ ) {
+ TextButton(
+ onClick =
+ {
+ startSettingsActivity(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
+ }
+ ) {
+ Text(text = stringResource(id = string.button_label_app_settings))
+ }
+ }
+ }
+
+ TroubleshootingItemType.FULL_SCREEN_INTENT -> {
+ TroubleShootItem(
+ title = stringResource(id = R.string.troubleshooting_incoming_call_valid_title),
+ description = stringResource(id = R.string.troubleshooting_incoming_call_valid_description),
+ descriptionInvalid = stringResource(id = R.string.troubleshooting_incoming_call_invalid_description),
+ valid = fullScreenIntentState.valid,
+ checkState = fullScreenIntentState
+ ) {
+ TextButton(
+ onClick =
+ {
+ startSettingsActivity(Settings.ACTION_MANAGE_APP_USE_FULL_SCREEN_INTENT)
+ }
+ ) {
+ Text(text = stringResource(id = string.button_label_app_settings))
+ }
+ }
+ }
+
+ TroubleshootingItemType.PERMANENT_WEBSOCKET -> {
+ TroubleShootItem(
+ title = stringResource(id = string.troubleshooting_google_valid_title),
+ description = stringResource(id = string.troubleshooting_google_valid_description),
+ descriptionInvalid = stringResource(id = string.troubleshooting_google_invalid_description),
+ valid = permanentSocketState.valid,
+ checkState = permanentSocketState
+ ) {
+ TextButton(
+ onClick =
+ {
+ SettingsActivity.setUsePermanentWebSocket(true)
+ startService(
+ Intent(
+ this@TroubleshootingActivity,
+ UnifiedForegroundService::class.java
+ )
+ )
+ permanentSocketState.refreshStatus()
+ }
+ ) {
+ Text(text = stringResource(id = string.button_label_enable_websocket))
+ }
+ }
+ }
+
+ TroubleshootingItemType.BACKUPS -> {
+ val backupsStateInfo = remember(backupState.getStatus()) {
+ getBackupStateInfo()
+ }
+ TroubleShootItem(
+ title = stringResource(id = R.string.button_label_backup_settings),
+ description = stringResource(id = R.string.troubleshooting_backup_valid_description),
+ titleInvalid = backupsStateInfo?.title,
+ descriptionInvalid = backupsStateInfo?.description,
+ valid = backupState.valid,
+ checkState = backupState
+ ) {
+ TextButton(
+ onClick =
+ {
+ val intent = Intent(
+ this@TroubleshootingActivity,
+ SettingsActivity::class.java
+ )
+ intent.putExtra(
+ SettingsActivity.SUB_SETTING_PREF_KEY_TO_OPEN_INTENT_EXTRA,
+ SettingsActivity.PREF_HEADER_KEY_BACKUP
+ )
+ startActivity(intent)
+
+ }
+ ) {
+ Text(text = stringResource(id = string.button_label_backup_settings))
+ }
+ }
+ }
+
+ TroubleshootingItemType.CONNECTIVITY -> {
+ var ping by remember {
+ mutableStateOf("-")
+ }
+ val connectivityState by AppSingleton.getWebsocketConnectivityStateLiveData()
+ .observeAsState()
+ PingListener(connectivityState == 2) {
+ ping = when (it) {
+ -1L -> {
+ getString(R.string.label_over_max_ping_delay, 5)
+ }
+
+ 0L -> {
+ "-"
+ }
+
+ else -> {
+ getString(R.string.label_ping_delay, it)
+ }
+ }
+ }
+ TroubleShootItem(
+ title = when (connectivityState) {
+ 2 -> stringResource(id = R.string.label_ping_connectivity_connected)
+ 1 -> stringResource(id = R.string.label_ping_connectivity_connecting)
+ else -> stringResource(id = R.string.label_ping_connectivity_none)
+ },
+ description = ping, valid = connectivityState == 2
+ ) {
+ }
+ }
+
+
+ TroubleshootingItemType.STORAGE -> {
+ TroubleShootItem(
+ title = stringResource(id = string.troubleshooting_storage_valid_title),
+ description = stringResource(
+ id = string.troubleshooting_storage_valid_description,
+ Formatter.formatShortFileSize(
+ context,
+ AvailableSpaceHelper.getAvailableSpace() ?: 0
+ )
+ ),
+ titleInvalid = stringResource(id = string.troubleshooting_storage_invalid_title),
+ descriptionInvalid = stringResource(
+ id = string.troubleshooting_storage_invalid_description,
+ Formatter.formatShortFileSize(
+ context,
+ AvailableSpaceHelper.getAvailableSpace() ?: 0
+ )
+ ),
+ valid = storageState.valid,
+ checkState = storageState
+ ) {
+ TextButton(
+ onClick =
+ {
+ if (VERSION.SDK_INT >= VERSION_CODES.N_MR1) {
+ startActivity(Intent(StorageManager.ACTION_MANAGE_STORAGE))
+ }
+ }
+ ) {
+ Text(text = stringResource(id = string.button_label_manage_storage))
+ }
+ }
+ }
+ }
+ }
+
+ AppVersionHeader(betaEnabled = SettingsActivity.getBetaFeaturesEnabled())
+ }
+ }
+ }
+ }
+}
+
+fun Activity.startSettingsActivity(action: String = ACTION_APPLICATION_DETAILS_SETTINGS) =
+ startActivity(
+ Intent(
+ action,
+ Uri.fromParts("package", packageName, null)
+ ).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ )
+
+private fun openAppNotificationSettings(context: Context) {
+ val intent = Intent().apply {
+ when {
+ VERSION.SDK_INT >= VERSION_CODES.O -> {
+ action = ACTION_APP_NOTIFICATION_SETTINGS
+ putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
+ }
+
+ else -> {
+ action = "android.settings.APP_NOTIFICATION_SETTINGS"
+ putExtra("app_package", context.packageName)
+ putExtra("app_uid", context.applicationInfo.uid)
+ }
+ }
+ }
+ context.startActivity(intent)
+}
\ No newline at end of file
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/TroubleshootingComponents.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/TroubleshootingComponents.kt
new file mode 100644
index 00000000..3d72fc6c
--- /dev/null
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/TroubleshootingComponents.kt
@@ -0,0 +1,344 @@
+/*
+ * Olvid for Android
+ * Copyright © 2019-2023 Olvid SAS
+ *
+ * This file is part of Olvid for Android.
+ *
+ * Olvid is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * Olvid is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Olvid. If not, see .
+ */
+
+package io.olvid.messenger.troubleshooting
+
+import android.content.res.Configuration
+import android.os.Build
+import android.os.Build.VERSION
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.ClickableText
+import androidx.compose.material.Text
+import androidx.compose.material.TextButton
+import androidx.compose.material.ripple.rememberRipple
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Alignment.Companion.CenterHorizontally
+import androidx.compose.ui.Alignment.Companion.CenterVertically
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.rotate
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
+import io.olvid.messenger.BuildConfig
+import io.olvid.messenger.R
+import io.olvid.messenger.R.color
+import io.olvid.messenger.R.drawable
+import io.olvid.messenger.R.string
+import io.olvid.messenger.customClasses.formatMarkdown
+import io.olvid.messenger.main.Utils
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlin.time.Duration.Companion.seconds
+
+@Composable
+fun AppVersionHeader(betaEnabled: Boolean) {
+ val context = LocalContext.current
+ var uptime by remember {
+ mutableStateOf(Utils.getUptime(context))
+ }
+ LaunchedEffect(Unit) {
+ while (true) {
+ delay(1.seconds)
+ uptime = Utils.getUptime(context)
+ }
+ }
+ Column {
+ Text(
+ modifier = Modifier
+ .widthIn(max = 400.dp)
+ .fillMaxWidth()
+ .padding(start = 16.dp, end = 16.dp, bottom = 8.dp),
+ text = AnnotatedString(stringResource(
+ string.troubleshooting_header,
+ BuildConfig.VERSION_NAME + if (betaEnabled) " beta" else "",
+ BuildConfig.VERSION_CODE,
+ "${Build.BRAND} ${Build.MODEL}",
+ VERSION.SDK_INT,
+ uptime
+ )).formatMarkdown(),
+ color = colorResource(id = color.almostBlack),
+ fontSize = 16.sp,
+ )
+ }
+}
+
+@Composable
+fun FaqLinkHeader(openFaq: () -> Unit) {
+ Column {
+ Text(
+ modifier = Modifier
+ .widthIn(max = 400.dp)
+ .fillMaxWidth()
+ .padding(start = 16.dp, end = 16.dp),
+ text = stringResource(id = string.troubleshooting_faq_description),
+ color = colorResource(id = color.almostBlack),
+ fontSize = 16.sp,
+ )
+ ClickableText(
+ modifier = Modifier.align(CenterHorizontally)
+ .padding(8.dp),
+ text = AnnotatedString(
+ text = stringResource(id = R.string.troubleshooting_faq_link),
+ spanStyle = SpanStyle(color = colorResource(id = color.olvid_gradient_light))
+ ),
+ style = TextStyle(
+ fontSize = 14.sp,
+ fontWeight = FontWeight(400),
+ color = Color(0xFF8B8D97),
+ textAlign = TextAlign.Center
+ )
+ ) {
+ openFaq()
+ }
+ }
+}
+
+
+@Composable
+fun TroubleShootItem(
+ title: String,
+ description: String,
+ titleInvalid: String? = null,
+ descriptionInvalid: String? = null,
+ valid: Boolean,
+ critical: Boolean = true,
+ checkState: CheckState? = null,
+ actions: @Composable RowScope.() -> Unit
+) {
+ var expanded by rememberSaveable {
+ mutableStateOf(valid.not())
+ }
+ val mute: Boolean by checkState?.let{ it.isMute.collectAsState(true) } ?: remember { mutableStateOf(false) }
+ val borderWidth: Float by animateFloatAsState(targetValue = if (critical && valid.not() && mute == false) 2f else 1f)
+ val borderColor: Color by animateColorAsState(targetValue = if (critical && valid.not() && mute == false) colorResource(id = R.color.red) else Color(0x6E111111))
+ Column(
+ modifier = Modifier
+ .widthIn(max = 400.dp)
+ .border(
+ border = BorderStroke(borderWidth.dp, borderColor),
+ shape = RoundedCornerShape(12.dp)
+ )
+ .clip(
+ shape = RoundedCornerShape(12.dp)
+ )
+ .background(
+ color = colorResource(id = color.itemBackground),
+ shape = RoundedCornerShape(12.dp)
+ )
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null
+ ) {
+ expanded = !expanded
+ }
+ .padding(top = 8.dp, start = 16.dp, end = 16.dp)
+ ) {
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.Top
+ )
+ {
+ Column(
+ modifier = Modifier.weight(1f, fill = true),
+ ) {
+ Row {
+ val rotation: Float by animateFloatAsState(targetValue = if (expanded) 90f else 0f)
+ Image(
+ modifier = Modifier
+ .padding(top = 1.dp)
+ .size(12.dp)
+ .rotate(degrees = rotation)
+ .align(CenterVertically),
+ painter = painterResource(id = R.drawable.ic_chevron_right_compact),
+ contentDescription = "")
+ Text(
+ modifier = Modifier
+ .padding(start = 8.dp)
+ .weight(1f, true)
+ .align(CenterVertically),
+ text = if (valid) title else titleInvalid ?: title,
+ color = colorResource(id = color.almostBlack),
+ fontSize = 16.sp,
+ fontWeight = FontWeight.Normal,
+ )
+ Image(
+ modifier = Modifier
+ .size(32.dp),
+ painter = painterResource(id = if (valid) drawable.ic_ok_green else drawable.ic_error_outline),
+ colorFilter = ColorFilter.tint(color = colorResource(id = R.color.golden))
+ .takeIf { valid.not() && critical.not() },
+ contentDescription = ""
+ )
+ }
+ AnimatedVisibility(visible = expanded) {
+ Text(
+ modifier = Modifier.padding(top = 4.dp, bottom = if (valid) 4.dp else 0.dp),
+ text = if (valid) description else descriptionInvalid ?: description,
+ color = colorResource(id = color.greyTint),
+ fontSize = 13.sp,
+ fontWeight = FontWeight.Normal,
+ lineHeight = 18.sp,
+ )
+ }
+ }
+ }
+ AnimatedVisibility(visible = valid.not()) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .heightIn(min = 8.dp),
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ checkState?.let { checkState ->
+ val coroutineScope = rememberCoroutineScope()
+ val interactionSource = remember { MutableInteractionSource() }
+ Image(
+ modifier = Modifier
+ .align(CenterVertically)
+ .clickable(
+ interactionSource = interactionSource,
+ indication = rememberRipple(bounded = false)
+ ) { coroutineScope.launch { checkState.updateMute(mute.not()) } },
+ painter = painterResource(id = drawable.ic_notification_muted),
+ colorFilter = ColorFilter.tint(colorResource(id = R.color.almostBlack)),
+ contentDescription = "mute",
+ alpha = if (mute) 1f else 0.3f
+ )
+ AnimatedVisibility(visible = mute) {
+ Text(
+ modifier = Modifier
+ .clickable(
+ interactionSource = interactionSource,
+ indication = null
+ ) { coroutineScope.launch { checkState.updateMute(mute.not()) } }
+ .padding(start = 4.dp),
+ text = stringResource(id = R.string.troubleshooting_ignored),
+ fontSize = 12.sp,
+ )
+ }
+ }
+ Spacer(modifier = Modifier.weight(1f, true))
+ actions()
+ }
+ }
+ AnimatedVisibility(visible = valid) {
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+ }
+}
+
+enum class TroubleshootingItemType {
+ NOTIFICATIONS,
+ CAMERA,
+ MICROPHONE,
+ BATTERY_OPTIMIZATION,
+ BACKGROUND_RESTRICTION,
+ ALARM,
+ FULL_SCREEN_INTENT,
+ PERMANENT_WEBSOCKET,
+ BACKUPS,
+ CONNECTIVITY,
+ STORAGE,
+}
+
+@Preview
+@Composable
+fun AppVersionHeaderPreview() {
+ AppCompatTheme {
+ AppVersionHeader(true)
+ }
+}
+
+@Preview(locale = "fr-rFR")
+@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
+@Composable
+fun TroubleShootItemPreview() {
+ AppCompatTheme {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .wrapContentHeight()
+ .padding(16.dp)
+ ) {
+ TroubleShootItem(
+ title = "Title",
+ description = "description",
+ valid = false,
+ critical = true,
+ checkState = CheckState(
+ "test",
+ TroubleshootingDataStore(LocalContext.current),
+ ) { true }
+ ) {
+ TextButton(
+ shape = RoundedCornerShape(size = 8.dp),
+ onClick = {}
+ ) {
+ Text(text = stringResource(id = string.troubleshooting_request_permission))
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/TroubleshootingDataStore.kt b/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/TroubleshootingDataStore.kt
new file mode 100644
index 00000000..1741dcd1
--- /dev/null
+++ b/obv_messenger/app/src/main/java/io/olvid/messenger/troubleshooting/TroubleshootingDataStore.kt
@@ -0,0 +1,66 @@
+/*
+ * Olvid for Android
+ * Copyright © 2019-2023 Olvid SAS
+ *
+ * This file is part of Olvid for Android.
+ *
+ * Olvid is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * Olvid is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Olvid. If not, see .
+ */
+
+package io.olvid.messenger.troubleshooting
+
+import android.content.Context
+import androidx.datastore.preferences.core.booleanPreferencesKey
+import androidx.datastore.preferences.core.edit
+import androidx.datastore.preferences.core.emptyPreferences
+import androidx.datastore.preferences.preferencesDataStore
+import io.olvid.engine.Logger
+import kotlinx.coroutines.flow.catch
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.map
+import java.io.IOException
+
+class TroubleshootingDataStore(private val context: Context) {
+ companion object {
+ private val Context.troubleshootingDataStore by preferencesDataStore("troubleshooting")
+ }
+
+ suspend fun load() = context.troubleshootingDataStore.data.catch { exception ->
+ if (exception is IOException) {
+ Logger.e("troubleshootingDatastore", exception)
+ } else {
+ Logger.e("unable to load troubleshootingDatastore")
+ }
+ emit(emptyPreferences())
+ }.first()
+
+ fun isMute(item: String) = context.troubleshootingDataStore.data.catch { exception ->
+ if (exception is IOException) {
+ Logger.e("troubleshootingDatastore", exception)
+ } else {
+ Logger.e("unable to read troubleshootingDatastore")
+ }
+ emit(emptyPreferences())
+ }.map { preferences ->
+ preferences[booleanPreferencesKey(item)] ?: false
+ }
+
+ suspend fun updateMute(item: String, value: Boolean) {
+ try {
+ context.troubleshootingDataStore.edit { preferences ->
+ preferences[booleanPreferencesKey(item)] = value
+ }
+ } catch (exception: Exception) {
+ }
+ }
+}
\ No newline at end of file
diff --git a/obv_messenger/app/src/main/res/anim/dismiss_from_fling_up.xml b/obv_messenger/app/src/main/res/anim/dismiss_from_fling_up.xml
new file mode 100644
index 00000000..f43db5a6
--- /dev/null
+++ b/obv_messenger/app/src/main/res/anim/dismiss_from_fling_up.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/obv_messenger/app/src/main/res/drawable/ic_chevron_right_compact.xml b/obv_messenger/app/src/main/res/drawable/ic_chevron_right_compact.xml
new file mode 100644
index 00000000..93f2432d
--- /dev/null
+++ b/obv_messenger/app/src/main/res/drawable/ic_chevron_right_compact.xml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/obv_messenger/app/src/main/res/drawable/ic_contacts_filter.xml b/obv_messenger/app/src/main/res/drawable/ic_contacts_filter.xml
new file mode 100644
index 00000000..5ef814a9
--- /dev/null
+++ b/obv_messenger/app/src/main/res/drawable/ic_contacts_filter.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/obv_messenger/app/src/main/res/layout/activity_main.xml b/obv_messenger/app/src/main/res/layout/activity_main.xml
index 4f52dcee..b50d8627 100644
--- a/obv_messenger/app/src/main/res/layout/activity_main.xml
+++ b/obv_messenger/app/src/main/res/layout/activity_main.xml
@@ -333,4 +333,8 @@
+
\ No newline at end of file
diff --git a/obv_messenger/app/src/main/res/menu/menu_main.xml b/obv_messenger/app/src/main/res/menu/menu_main.xml
index 4e774918..6c9619c4 100644
--- a/obv_messenger/app/src/main/res/menu/menu_main.xml
+++ b/obv_messenger/app/src/main/res/menu/menu_main.xml
@@ -17,4 +17,9 @@
android:orderInCategory="200"
android:title="@string/menu_action_storage_management"
app:showAsAction="never"/>
+
diff --git a/obv_messenger/app/src/main/res/values-fr/strings.xml b/obv_messenger/app/src/main/res/values-fr/strings.xml
index 6021dd22..f1cca7fa 100644
--- a/obv_messenger/app/src/main/res/values-fr/strings.xml
+++ b/obv_messenger/app/src/main/res/values-fr/strings.xml
@@ -50,7 +50,6 @@
Vous êtes sur le point d\'abandonner une invitation. Abandonner l\'invitation en cours\u00a0?
Vous utilisez Olvid pour Android\nVersion %1$s (build %2$d)\n\nAPI serveur\u00a0: %3$d\nSchéma DB moteur\u00a0: %4$d\nSchéma DB application\u00a0: %5$d\nApp démarrée depuis\u00a0: %6$s
Vous utilisez Olvid pour Android\nVersion %1$s (build %2$d)\n\nAPI serveur\u00a0: %3$d\nSchéma DB moteur\u00a0: %4$d\nSchéma DB application\u00a0: %5$d\n\nFonctionnalités additionnelles\u00a0: %6$s\nApp démarrée depuis\u00a0: %7$s
- L\'exécution en arrière-plan semble avoir été limitée pour Olvid. L\'application ne pourra fonctionner normalement tant qu\'elle fait partie de la liste des applications dont l\'accès est limité.\n\nVous pouvez modifier ce réglage depuis :\nParamètres\u00a0d\'Olvid\u00a0> Options\u00a0avancées\u00a0> Batterie\u00a0> Restriction\u00a0de\u00a0l\'activité\u00a0en\u00a0arrière-plan
Souhaitez-vous présenter %1$s à %2$s\u00a0?
Souhaitez-vous présenter %1$s aux %2$d contacts suivants\u00a0?\n\n%3$s
Souhaitez-vous supprimer définitivement la pièce jointe %1$s de ce message\u00a0?\nCette action est irréversible.
@@ -65,7 +64,6 @@
Souhaitez-vous réellement redémarrer le protocole d\'établissement de canal de discussion sécurisé en cours ?
Interrompre l\'invitation\u00a0?
À propos d\'Olvid
- Restriction de l\'activité en arrière plan
Présentation de contacts
Supprimer la pièce jointe
Supprimer la discussion
@@ -88,15 +86,17 @@
Partager avec…
Créer un raccourci vers…
Choisissez un nom qui sera affiché chez vos contacts. Ces informations ne seront jamais envoyées aux serveurs d\'Olvid.
- Cet écran affiche la liste des contacts auxquels vous faites confiance.\n\nPour ajouter de nouveaux contacts, appuyez sur le + bleu en bas de cet écran.
- Cet écran affiche vos discussions en cours.
- Cet écran affiche la liste des groupes auxquels vous appartenez.
- Cet écran affiche la liste des invitations reçues ou envoyées.
+ Aucun contact pour le moment
+ Apuyez sur le + bleu ci-dessous pour ajouter un contact.
+ Aucune discussion pour le moment
+ Les discussions avec vos contacts et groupes s\'afficheront ici.
+ Aucun groupe pour le moment
+ Appuyez sur \"Nouveau groupe\" pour en créer un.
L\'administrateur du groupe a mis à jour la Group Card. L\'ancienne version et la nouvelle sont affichées ci-dessous.\n\nActualisez les informations du groupe en cliquant «\u00a0mettre à jour\u00a0».
Un administrateur a mis à jour les détails du groupe. La nouvelle version est affichée ci-dessous.\n\nUtilisez cette version en appuyant sur «\u00a0mettre à jour\u00a0».
Votre contact a mis à jour son Olvid Card. L\'ancienne version et la nouvelle sont affichées ci-dessous.\n\nActualisez les informations de votre contact en cliquant "mettre à jour".
Un canal sécurisé est actuellement en cours d\'établissement. Vous ne pouvez pas entamer de discussion avec ce contact tant que cela n\'est pas terminé. Ce processus devrait seulement prendre quelques secondes si vous et votre contact êtes tous deux en ligne.\n\nSi vous pensez que quelque chose s\'est mal passé, vous pouvez redémarrer l\'établissement de canal.
- Aucun contact ne correspond à la recherche
+ Aucun contact trouvé pour cette recherche
%1$s : %2$s
Société (facultatif)
Écrire un message
@@ -292,9 +292,6 @@
Rechercher un contact…
Recherche
Échec de téléchargement des messages
- Services Google Play absents
- Il semble que les services Google Play ne sont pas installés sur votre appareil. Olvid s\'appuie sur ces services pour recevoir les notifications de messages entrants. Sans eux, vous ne recevrez vos nouveaux messages que quand Olvid est en avant-plan.\n\nVous pouvez à la place activer une connexion WebSocket permanente aux serveurs d\'Olvid qui pourra jouer le même rôle (mais risque de vider votre batterie plus vite).
- Fonction d\'économie d\'énergie
L\'économie d\'énergie sur Android fonctionne en retardant certains événements d\'arrière-plan pour les exécuter par lots. Cela peut retarder la réception de messages Olvid entrants.\n\nPour être certain de recevoir vos messages au plus vite, vous pouvez autoriser Olvid à «\u00a0toujours fonctionner en arrière-plan\u00a0». Appuyez sur «\u00a0Paramètres\u00a0» puis «\u00a0Autoriser\u00a0».
Ne plus afficher ce message
Paramètres
@@ -504,7 +501,8 @@
Sonnerie
Vibreur
Notifications d\'appel entrant
- Cet écran affiche vos appels émis et reçus.
+ Aucun appel pour le moment
+ Vos appels émis et reçus s\'afficheront ici.
%1$s (durée %2$02dmin\u00a0%3$02dsec)
Effacer le journal
(durée %1$02dmin\u00a0%2$02dsec)
@@ -1035,8 +1033,6 @@
Serveurs TURN de Singapour
Autorisation d\'accés aux appareils bluetooth
Si vous souhaitez utiliser des écouteurs bluetooth pour vos appels Olvid, vous devez autoriser Olvid à détecter et se connecter aux appareils à proximité.
- Autorisation de définir des alarmes
- Olvid utilise des alarmes pour programmer de façon précise le verrouillage de l\'écran et l\'effacement des messages éphémères.\nL\'autorisation de définir de telles alarmes a été révoquée, ce qui peut entrainer le dysfonctionnement de ces fonctions de sécurité.\n\nNous vous encourageons fortement à autoriser la définition d\'alarmes et rappels en appuyant le bouton «\u00a0paramètres d\'Olvid\u00a0» ci-dessous.
%1$d contacts
%1$d contacts du groupe %2$s
Mise à jour requise
@@ -1057,16 +1053,12 @@
Olvid a détecté une modification de la clé de signature cryptographique de votre fournisseur d\'identités. Cela ne devrait normalement jamais arriver.\n\nVeuillez contacter votre administrateur et n\'appuyer sur «\u00a0mettre à jour la clé\u00a0» que s\'il peut confirmer que cette modification est intentionnelle. Dans le doute, appuyez sur «\u00a0annuler\u00a0».
Fournisseur d\'identités supprimé
Il semble que votre compte a été supprimé du fournisseur d\'identités de votre société. Si vous avez quitté votre entreprise, ceci est normal et vous pouvez continuer à utiliser Olvid en tant qu\'utilisateur gratuit.\n\nSi vous pensez que c\'est une erreur, veuillez contacter votre administrateur pour re-enregistrer ce fournisseur d\'identités dans Olvid.
- Montrez moi
Paramétrer les sauvegardes
Il est temps de configurer les sauvegardes\u00a0!
- Pourquoi configurer les sauvegardes 🤔\u00a0?
- Si vous veniez à égarer votre appareil ou à désinstaller Olvid par erreur, vous perdriez votre ID Olvid, l\'intégralité de vos contacts et tous vos groupes 😱. Fort heureusement, il est possible de les sauvegarder de façon sécurisée 😅. Appuyez sur «\u00a0paramétrer les sauvegardes\u00a0» pour commencer.
+ Si vous veniez à égarer votre appareil ou à désinstaller Olvid par erreur, vous perdriez votre ID Olvid, l\'intégralité de vos contacts et tous vos groupes 😱. Fort heureusement, il est possible de les sauvegarder de façon sécurisée 😅. Appuyez sur «\u00a0paramétrer les sauvegardes\u00a0» pour commencer.
Il est temps de faire une sauvegarde\u00a0!
Pour ne perdre aucun contact, nous vous recommandons d\'activer les sauvegardes automatiques vers Google Drive. Rassurez-vous, elles sont chiffrées 🤓\u00a0! Sinon, vous pouvez aussi effectuer des sauvegardes manuelles régulièrement. Appuyez sur «\u00a0paramétrer les sauvegardes\u00a0» pour commencer.
- Cela fait plus d\'une semaine que vous n\'avez pas effectué de sauvegarde. C\'est probablement le bon moment pour en faire une nouvelle. Gardez à l\'esprit qu\'une vieille sauvegarde peut ne pas contenir tous vos contacts les plus récents.
- Vous souvenez-vous de votre clé de sauvegarde\u00a0?
- Avoir une sauvegarde à jour est essentiel, mais il vous faut votre clé de sauvegarde pour la restaurer\u00a0! Appuyez sur «\u00a0paramétrer les sauvegardes\u00a0» pour vérifier votre clé. Si vous avez perdu cette clé, pas d\'inquiétude, vous pourrez en générer une nouvelle.
+ Cela fait plus d\'une semaine que vous n\'avez pas effectué de sauvegarde. C\'est probablement le bon moment pour en faire une nouvelle. Gardez à l\'esprit qu\'une vieille sauvegarde peut ne pas contenir vos contacts les plus récents. Appuyez sur «\u00a0paramétrer les sauvegardes\u00a0» pour commencer.
Ouvrir avec une application externe
Vous êtes sur le point d\'ouvrir un fichier avec une application externe. Le contenu de ce fichier sera exposé à l\'extérieur d\'Olvid.\nSouhaitez-vous poursuivre\u00a0?
Débloquer la discussion
@@ -1221,8 +1213,8 @@
Réseau\u00a0: AUCUN
Réseau\u00a0: CONNEXION…
Réseau\u00a0: CONNECTÉ
- ping\u00a0: %1$dms
- ping\u00a0: >%1$ds
+ Ping\u00a0: %1$dms
+ Ping\u00a0: >%1$ds
Copier le texte du message
Copier texte et pièces jointes
Message copié depuis Olvid
@@ -1275,6 +1267,7 @@
Autres
Tout
Gestion du stockage
+ Dépannage
Aucune pièce jointe à afficher
Ordre de tri
Trier par date (récents en premier)
@@ -1543,11 +1536,6 @@
En attente
Langue de l\'application
Langue par défaut de l\'appareil
- Notifications désactivées
- Il semble que les notifications soient désactivées pour Olvid. Si vous ne les activez pas, vous ne serez pas notifié quand un message de vos contacts vous arrive.\n\nNous vous recommandons d\'«\u00a0ouvrir les paramètres\u00a0» et de réactiver les notifications.
- Soyez notifié\u00a0!
- Pour être notifié quand un contact vous envoie un message, vous devez autoriser Olvid à vous envoyer des notifications.
- Passer
Bloquer les connexions utilisant un certificat inconnu
Jamais
Toujours
@@ -1676,8 +1664,6 @@
Changer de service de cartographie
Groupe en lecture seule
Quand cette option est activée, seuls les administrateurs peuvent poster des messages dans ce groupe
- Notifications d\'appel
- La réception d\'un appel peut nécessiter l\'affichage d\'une notification d\'appel entrant en plein écran. Olvid n\'a pas pour l\'instnt l\'autorisation d\'afficher une telle notification. Appuyez sur «\u00a0Paramètres d\'Olvid\u00a0» pour ouvrir l\'écran de paramètres correspondant.
Ajouter un appareil
Bien le bonjour !
Bienvenue parmi nous
@@ -1758,6 +1744,46 @@
Séquestre de clé réussi
Échec du séquestre de clé
+ Notifications
+ Pour être notifié quand un contact vous envoie un message, vous devez autoriser Olvid à afficher des notifications.
+ Notifications désactivées
+ Les notifications sont actuellement désactivées pour Olvid. Si vous ne les activez pas, vous ne serez pas notifié quand vous recevez un message de vos contacts.
+ Accès à l\'appareil photo
+ Donner accès à l\'appareil photo à Olvid vous permet d\'envoyer des photos prises directement depuis Olvid ou de scanner l\'ID d\'autres utilisateurs.
+ Accès à l\'appareil photo interdit
+ Enregistrement audio
+ Accorder à Olvid l\'accès au micro vous permet d\'enregistrer des messages vocaux et est nécessaire pour l\'émission et la réception d\'appels sécurisés.
+ Enregistrement audio désactivé
+ Fonction d\'économie d\'énergie
+ L\'économie d\'énergie sur Android fonctionne en retardant certains événements d\'arrière-plan pour les exécuter par lots. Cela peut retarder la réception de messages Olvid ou d\'appels.\n\nOlvid est actuellement autorisée à « toujours fonctionner en arrière-plan ».
+ L\'économie d\'énergie sur Android fonctionne en retardant certains événements d\'arrière-plan pour les exécuter par lots. Cela peut retarder la réception de messages Olvid ou d\'appels.\n\nPour être certain de recevoir vos messages au plus vite, vous devriez autoriser Olvid à «\u00a0toujours fonctionner en arrière-plan\u00a0».
+ Restriction d\'arrière plan
+ Olvid doit être autorisée à s\'exécuter en arrière-plan pour bien fonctionner. Activer les restrictions d\'arrière-plan empêcherait, par exemple, de recevoir les messages en arrière plan.
+ Les « restriction d\'arrière-plan » s\'appliquent actuellement à Olvid. L\'application ne pourra pas fonctionner normalement tant qu\'elle fait partie de la liste des « applications restreintes ».\n\nVous pouvez corriger cela en appuyant sur « Paramètres d\'Olvid » et en allant des la section utilisation de la batterie.
+ Autorisation de définir des alarmes
+ Olvid utilise des alarmes pour programmer de façon précise le verrouillage de l\'écran et l\'effacement des messages éphémères. Cette autorisation est réglée correctement.
+ Olvid utilise des alarmes pour programmer de façon précise le verrouillage de l\'écran et l\'effacement des messages éphémères.\nL\'autorisation de définir de telles alarmes a été révoquée, ce qui peut entrainer le dysfonctionnement de ces fonctions de sécurité.\n\nNous vous encourageons fortement à autoriser la définition d\'alarmes et rappels en appuyant sur «\u00a0paramètres d\'Olvid\u00a0».
+ Notifications d\'appel
+ La réception d\'un appel nécessite l\'affichage d\'une notification d\'appel entrant en plein écran. Cette autorisation est réglée correctement.
+ La réception d\'un appel nécessite l\'affichage d\'une notification d\'appel entrant en plein écran. Olvid n\'a pas pour l\'instnt l\'autorisation d\'afficher une telle notification. Appuyez sur «\u00a0Paramètres d\'Olvid\u00a0» pour autoriser les notifications en plein écran.
+ Services Google Play absents
+ Une connexion WebSocket permanente aux serveurs d\'Olvid est correctement configurée.
+ Les services Google Play ne sont pas disponibles sur votre appareil. Olvid s\'appuie sur ces services pour recevoir les notifications de messages entrants. Sans eux, vous ne recevrez vos nouveaux messages que quand Olvid est en avant-plan.\n\nVous pouvez à la place activer une connexion WebSocket permanente aux serveurs d\'Olvid qui pourra jouer le même rôle (mais risque de vider votre batterie légèrement plus vite).
+ Activer la WebSocket
+ Espace de stockage
+ %1$s d\'espace disponible sur votre appareil.
+ Espace de stockage presque plein
+ Seulement %1$s d\'espace libre sur votre appareil.\n\nVotre appareil n\'a presque plus d\'espace de stockage disponible. Attendez-vous à rencontrer des erreurs inattendues lors de l\'envoi ou de la réception de fichiers.\n\nCeci est probablement le bon moment pour libérer de l\'espace sur votre appareil.
+ **Version d\'Olvid** : %1$s (build %2$d)\n**Modèle d\'appareil** : %3$s\n**API Android** : %4$s\n**App démarrée depuis** : %5$s
+ Besoin d\'aide avec Olvid ? Consultez notre assistance :
+ https://olvid.io/faq
+ Demander l\'autorisation
+ Les sauvegardes Olvid sont correctement configurées. Assurez-vous que vous savez où est votre clé de sauvegarde et vérifiez que vous avez accès à une sauvegarde récente.\n\nIl est recommandé de vérifier votre clé de sauvegarde de temps en temps, juste pour être certain de savoir quoi faire si vous perdez votre appareil 😅.
+ Améliorez votre expérience Olvid ?
+ Allons-y
+ Ignoré
+
+
- %1$d groupe commun
diff --git a/obv_messenger/app/src/main/res/values-night/bool.xml b/obv_messenger/app/src/main/res/values-night/bool.xml
new file mode 100644
index 00000000..0a075e0e
--- /dev/null
+++ b/obv_messenger/app/src/main/res/values-night/bool.xml
@@ -0,0 +1,4 @@
+
+
+ false
+
\ No newline at end of file
diff --git a/obv_messenger/app/src/main/res/values-night/colors.xml b/obv_messenger/app/src/main/res/values-night/colors.xml
index c7db4e9a..bf5a9eea 100644
--- a/obv_messenger/app/src/main/res/values-night/colors.xml
+++ b/obv_messenger/app/src/main/res/values-night/colors.xml
@@ -34,6 +34,7 @@
#303030
+ #171717
#99303030
#c3d9e6
\ No newline at end of file
diff --git a/obv_messenger/app/src/main/res/values/bool.xml b/obv_messenger/app/src/main/res/values/bool.xml
new file mode 100644
index 00000000..f799eca8
--- /dev/null
+++ b/obv_messenger/app/src/main/res/values/bool.xml
@@ -0,0 +1,4 @@
+
+
+ true
+
\ No newline at end of file
diff --git a/obv_messenger/app/src/main/res/values/colors.xml b/obv_messenger/app/src/main/res/values/colors.xml
index 5703e848..2eeac27e 100644
--- a/obv_messenger/app/src/main/res/values/colors.xml
+++ b/obv_messenger/app/src/main/res/values/colors.xml
@@ -44,6 +44,7 @@
#ffffff
+ #ffffff
#99ffffff
#0C2643
#ffffff
diff --git a/obv_messenger/app/src/main/res/values/strings.xml b/obv_messenger/app/src/main/res/values/strings.xml
index c2761784..6898cef8 100644
--- a/obv_messenger/app/src/main/res/values/strings.xml
+++ b/obv_messenger/app/src/main/res/values/strings.xml
@@ -22,9 +22,9 @@
Smaller than 100MB
Never download automatically
Always download automatically
- Abort
+ Abort
Accept
- App Settings
+ App settings
Cancel
Create Group
Discard
@@ -51,7 +51,6 @@
You are about to discard an invitation. Proceed?
You are using Olvid for Android\nVersion %1$s (build %2$d)\n\nServer API: %3$d\nEngine DB schema: %4$d\nApplication DB schema: %5$d\nApp running for: %6$s
You are using Olvid for Android\nVersion %1$s (build %2$d)\n\nServer API: %3$d\nEngine DB schema: %4$d\nApplication DB schema: %5$d\n\nExtra features: %6$s\nApp running for: %7$s
- It appears that the Olvid app has Background Restriction enabled. Olvid will not be able to function properly until you remove it from the Restricted Apps list.\n\nYou can access this settings from:\nApp\u00a0Settings\u00a0> Advanced\u00a0> Battery\u00a0> Background\u00a0restriction
Do you want to introduce %1$s and %2$s to one another?
Do you want to introduce %1$s to the %2$d following contacts?\n\n%3$s
Do you really want to delete attachment %1$s from this message?\nThis action cannot be undone.
@@ -66,7 +65,6 @@
Do you really wish to restart the ongoing secure discussion channel establishment protocol?
Abort Invitation?
About Olvid
- Olvid is Background Restricted
Contact Introduction
Delete Attachment
Delete Discussion
@@ -89,10 +87,12 @@
Share with…
Create shortcut to…
Please enter a name which will be displayed to your contacts. These details will never be sent to Olvid\'s servers.
- This screen displays the list of contacts you trust.\n\nTo add new contacts, press the blue + at the bottom of this screen.
- This screen displays your ongoing discussions.
- This screen displays the list of groups you belong to.
- This screen displays the list of invitations you have received or sent.
+ No contacts for now
+ Press the blue + below to add your first contact.
+ No discussions for now
+ Contact and group discussions will appear here.
+ No groups for now
+ Press \"New group\" to create your first group.
The group manager updated the Group Card. Both the old and new versions are shown below.\n\nPress \"update\" to update the group\'s information.
One of the group administrators updated the group details. The new version is shown above.\n\nPress \"update\" to use this new version.
Your contact updated their Olvid Card. Both the old and new versions are shown below.\n\nPress \"update\" to update your contact\'s information with this new version.
@@ -297,9 +297,6 @@
Search Contact Name…
Search
Failed to get messages
- Google Play Services Unavailable
- It seems the Google Play Services are not installed on your device. Olvid relies on these services to be notified of incoming messages. Without them, you will only receive new messages when Olvid is in the foreground.\n\nYou may instead enable a permanent WebSocket connection to Olvid\'s servers that can serve the same purpose (but may drain your battery).
- Battery Optimization
Battery optimization on Android works by delaying certain background events to run them in a batch. This may delay the reception of inbound Olvid messages.\n\nTo ensure that your messages are received promptly, you should allow Olvid to \"always run in background\". Press \"Settings\" below and then \"Allow\".
Do not show this message again
Settings
@@ -510,7 +507,8 @@
Ringtone
Vibrator
Incoming call notifications
- This screen displays the list of calls you made or received.
+ No calls for now
+ Calls you make or receive will appear here.
%1$s (lasted %2$02dmin\u00a0%3$02dsec)
(lasted %1$02dmin\u00a0%2$02dsec)
(lasted %1$02dsec)
@@ -1042,8 +1040,6 @@
Singapore TURN servers
Request permission to access bluetooth devices
If you want to use a bluetooth headset for Olvid calls, you need to allow Olvid to \"find and connect to\" your nearby devices.
- Permission to set alarms
- Olvid relies on alarms to precisely schedule screen lock and ephemeral messages deletion.\nThe permission to set such alarms has been revoked, which may cause these security features to misbehave.\n\nWe strongly encourage you to allow settings alarms and reminders by pressing the \"app settings\" button below.
%1$d contacts
%1$d contacts in group %2$s
Update required
@@ -1064,16 +1060,12 @@
Olvid detected a change in the cryptographic signature key of your identity provider. This should normally never happen.\n\nPlease contact your administrator and only press \"update key\" if he can confirm the key change was intentional. If unsure, press \"cancel\".
Identity provider removed
It seems that your account was removed from your company\'s identity provider. If you left your company, this is normal and you may continue using Olvid as a free user.\n\nIf you believe this is an error, please contact your administrator to re-register this identity provider with Olvid.
- Show me
Setup backups
It\'s time to setup backups!
- Why should you setup backups 🤔?
- If you were to lose your device, or to uninstall Olvid by mistake, you would lose your Olvid ID, all your contacts, and all your groups 😱. Luckily for you, it is possible to setup secure backups 😅.\n\nPress \"setup backups\" to begin.
+ If you were to lose your device, or to uninstall Olvid by mistake, you would lose your Olvid ID, all your contacts, and all your groups 😱. Luckily for you, it is possible to setup secure backups 😅.\n\nPress \"Setup backups\" to begin.
It\'s backup time!
- In order not to lose any contact, we recommend you activate automatic backups to Google Drive. Don\'t worry, these backups are encrypted 🤓!\nOtherwise, you may also perform manual backups on a regular basis.\n\nPress \"setup backups\" to begin.
- It\'s been more than a week since your last backup. Now is probably a good time for a new one. Keep in mind that an old backup might not contain all your latest contacts.\n\nPress \"setup backups\" to begin.
- Do you remember your backup key?
- Having an up to date Olvid backup is essential, but you need your backup key to restore it!\n\nPress \"setup backups\" to verify your key. If you lost it, don\'t worry, you can generate a new one.
+ In order not to lose any contact, we recommend you activate automatic backups to Google Drive. Don\'t worry, these backups are encrypted 🤓!\nOtherwise, you may also perform manual backups on a regular basis.\n\nPress \"Setup backups\" to begin.
+ It\'s been more than a week since your last backup. Now is probably a good time for a new one. Keep in mind that an old backup might not contain your latest contacts.\n\nPress \"Setup backups\" to begin.
Keycloak configuration
Configuration link provided in the keycloak Olvid Management Console.
Disable new version notification
@@ -1240,8 +1232,8 @@
Connectivity: NONE
Connectivity: CONNECTING…
Connectivity: CONNECTED
- ping: %1$dms
- ping: >%1$ds
+ Ping: %1$dms
+ Ping: >%1$ds
Copy message text
Copy message and attachments
Message copied from Olvid
@@ -1295,6 +1287,7 @@
Other
All
Storage management
+ Troubleshooting
No attachment to display
Sort order
Sort by date (most recent first)
@@ -1568,11 +1561,6 @@
Default device language
English
Français
- Notifications disabled
- It appears that notifications are currently disabled for Olvid. Unless you enable them, you will not be notified when receiving a message from your contacts.\n\nWe recommend you tap \"open settings\" and toggle notifications back on.
- Get notified!
- To be notified when a contact sends you a message, you need to allow Olvid to send notifications.
- Skip
Block connections using an untrusted certificate
Never
Always
@@ -1706,8 +1694,6 @@
Change map provider
Read only group
When activated, only group admins can post messages in this group
- Incoming call notifications
- Incoming calls sometimes need to display a full screen \"answer call\" notification. Olvid currently does not have permission to open such full screen notifications. Press \"App Settings\" to open the relevant setting screen.
Add a device
Welcome!
Welcome to Olvid
@@ -1788,6 +1774,47 @@
Key escrow successful
Key escrow failed
+ Notifactions
+ To be notified when a contact sends you a message, you need to allow Olvid to display notifications.
+ Notifications disabled
+ Notifications are currently disabled for Olvid. Unless you enable them, you will not be notified when receiving a message from your contacts.
+ Camera access
+ Granting Olvid access to the camera allows you to send pictures taken directly from within Olvid, or to scan the ID of other users.
+ Camera access denied
+ Audio recording
+ Granting Olvid access to the microphone allows you to record voice messages and is required to start or receive secure calls.
+ Audio recording disabled
+ Battery optimization
+ Battery optimization on Android works by delaying certain background events to run them in a batch. This may delay the reception of inbound Olvid messages and calls.\n\nOlvid is currently allowed to \"always run in background\".
+ Battery optimization on Android works by delaying certain background events to run them in a batch. This may delay the reception of inbound Olvid messages and calls.\n\nTo ensure that your messages are received promptly, you should allow Olvid to \"always run in background\".
+ Background restriction
+ Olvid needs to be allowed to run in background to function properly. Enabling background restriction would, for example, prevent Olvid from receiving messages in the background.
+ Olvid has \"background restriction\" enabled. Olvid will not be able to function properly until you remove it from the \"restricted apps\" list.\n\nYou can fix this by pressing \"App settings\" and heading to the battery usage section.
+ Permission to set alarms
+ Olvid relies on alarms to precisely schedule screen lock and ephemeral messages deletion. This permission is set correctly.
+ Olvid relies on alarms to precisely schedule screen lock and ephemeral messages deletion.\nThe permission to set such alarms has been revoked, which may cause these security features to misbehave.\n\nWe strongly encourage you to allow setting alarms and reminders by pressing \"App settings\".
+ Incoming call notifications
+ Incoming calls need to display a full screen \"answer call\" notification. This permission is set correctly.
+ Incoming calls need to display a full screen \"answer call\" notification. Olvid currently does not have permission to open such full screen notifications. Press \"App settings\" to allow full scrren notifications.
+ Google Play Services unavailable
+ Permanent WebSocket connection to Olvid\'s servers is properly configured.
+ Google Play Services are not available on your device. Olvid relies on these services to be notified of incoming messages. Without them, you will only receive new messages when Olvid is in the foreground.\n\nYou may instead enable a permanent WebSocket connection to Olvid\'s servers that can serve the same purpose (but may slightly drain your battery).
+ Enable the WebSocket
+ Device storage
+ %1$s storage space available on your device.
+ Device storage almost full
+ Only %1$s storage space left on your device.\n\nYour device is running low on available space. Expect to run into unexpected errors when receiving or sending files with Olvid.\n\nThis is probably the right time to free up some space on your device.
+ **App version**: %1$s (build %2$d)\n**Device model**: %3$s\n**Android API**: %4$s\n**App running for**: %5$s
+ Need help with Olvid? Please read our FAQ:
+ https://olvid.io/faq
+ Request permission
+ Olvid backups are properly setup. Please make sure you know where your backup key is and check that you have access to up to date backup files.\n\nIt is a good idea to verify your backup key from time to time, just to be sure you\'ll know what to do if you lose your device 😅.
+ Want to improve your Olvid experience?
+ Let\'s go
+ Ignored
+
+
+
- 1 common group
- %1$d common groups
diff --git a/obv_messenger/app/src/main/res/values/styles.xml b/obv_messenger/app/src/main/res/values/styles.xml
index d23aa981..8b9d2359 100644
--- a/obv_messenger/app/src/main/res/values/styles.xml
+++ b/obv_messenger/app/src/main/res/values/styles.xml
@@ -33,6 +33,12 @@
- @color/olvid_gradient_light
+
+