diff --git a/.editorconfig b/.editorconfig
index 43461fa34..823298213 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -51,3 +51,4 @@ ktlint_standard_import-ordering = disabled
ktlint_standard_wrapping = enabled
ij_kotlin_allow_trailing_comma = false
ij_kotlin_allow_trailing_comma_on_call_site = false
+ktlint_function_naming_ignore_when_annotated_with = Composable
diff --git a/.idea/inspectionProfiles/ktlint.xml b/.idea/inspectionProfiles/ktlint.xml
index 7d04a74be..0938bfdc8 100644
--- a/.idea/inspectionProfiles/ktlint.xml
+++ b/.idea/inspectionProfiles/ktlint.xml
@@ -2,6 +2,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index eb9da6059..29eb613c7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -26,6 +26,7 @@ apply plugin: 'com.github.spotbugs'
apply plugin: 'io.gitlab.arturbosch.detekt'
apply plugin: "org.jlleitschuh.gradle.ktlint"
apply plugin: 'kotlinx-serialization'
+apply plugin: 'dagger.hilt.android.plugin'
android {
compileSdk 34
@@ -45,6 +46,7 @@ android {
flavorDimensions "default"
renderscriptTargetApi 19
renderscriptSupportModeEnabled true
+ javaCompileOptions.annotationProcessorOptions.arguments['dagger.hilt.disableModulesHaveInstallInCheck'] = 'true'
productFlavors {
// used for f-droid
@@ -121,6 +123,11 @@ android {
buildFeatures {
viewBinding true
buildConfig = true
+ compose = true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.13"
}
lint {
@@ -130,6 +137,9 @@ android {
htmlReport true
}
}
+kapt {
+ correctErrorTypes = true
+}
ext {
androidxCameraVersion = "1.3.4"
@@ -259,6 +269,7 @@ dependencies {
implementation "com.afollestad.material-dialogs:lifecycle:${materialDialogsVersion}"
implementation 'com.google.code.gson:gson:2.11.0'
+ implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
implementation "androidx.media3:media3-exoplayer:$media3_version"
implementation "androidx.media3:media3-ui:$media3_version"
@@ -280,6 +291,19 @@ dependencies {
implementation 'androidx.core:core-ktx:1.13.1'
+ //compose
+ implementation(platform("androidx.compose:compose-bom:2024.02.01"))
+ implementation("androidx.compose.ui:ui")
+ implementation 'androidx.compose.material3:material3'
+ implementation("androidx.compose.ui:ui-tooling-preview")
+ implementation 'androidx.activity:activity-compose:1.9.0'
+ implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.1'
+ debugImplementation("androidx.compose.ui:ui-tooling")
+
+ //tests
+ androidTestImplementation("androidx.compose.ui:ui-test-junit4")
+ debugImplementation("androidx.compose.ui:ui-test-manifest")
+
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:5.12.0'
androidTestImplementation 'org.mockito:mockito-android:5.12.0'
@@ -307,6 +331,11 @@ dependencies {
implementation 'com.github.nextcloud.android-common:ui:0.22.0'
implementation 'com.github.nextcloud-deps:android-talk-webrtc:121.6167.0'
+ implementation(platform("androidx.compose:compose-bom:2024.06.00"))
+ implementation("io.coil-kt:coil-compose:2.6.0")
+
+ implementation "com.google.dagger:hilt-android:$hilt_version"
+ kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}
tasks.register('installGitHooks', Copy) {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index dd0deb166..a92209bba 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -126,6 +126,9 @@
android:name=".account.WebViewLoginActivity"
android:theme="@style/AppTheme" />
+
+
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/ContactItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/ContactItem.java
deleted file mode 100644
index 998243f99..000000000
--- a/app/src/main/java/com/nextcloud/talk/adapters/items/ContactItem.java
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Nextcloud Talk - Android Client
- *
- * SPDX-FileCopyrightText: 2022 Marcel Hibbe
- * SPDX-FileCopyrightText: 2021 Andy Scherzinger
- * SPDX-FileCopyrightText: 2017 Mario Danic
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package com.nextcloud.talk.adapters.items;
-
-import android.annotation.SuppressLint;
-import android.os.Build;
-import android.text.TextUtils;
-import android.view.View;
-
-import com.nextcloud.talk.R;
-import com.nextcloud.talk.application.NextcloudTalkApplication;
-import com.nextcloud.talk.data.user.model.User;
-import com.nextcloud.talk.databinding.RvItemContactBinding;
-import com.nextcloud.talk.extensions.ImageViewExtensionsKt;
-import com.nextcloud.talk.models.json.participants.Participant;
-import com.nextcloud.talk.ui.theme.ViewThemeUtils;
-
-import java.util.List;
-import java.util.Objects;
-import java.util.regex.Pattern;
-
-import androidx.core.content.res.ResourcesCompat;
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
-import eu.davidea.flexibleadapter.items.IFilterable;
-import eu.davidea.flexibleadapter.items.ISectionable;
-import eu.davidea.viewholders.FlexibleViewHolder;
-
-public class ContactItem extends AbstractFlexibleItem implements
- ISectionable, IFilterable {
-
- private final Participant participant;
- private final User user;
- private GenericTextHeaderItem header;
- private final ViewThemeUtils viewThemeUtils;
- public boolean isOnline = true;
-
- public ContactItem(Participant participant,
- User user,
- GenericTextHeaderItem genericTextHeaderItem,
- ViewThemeUtils viewThemeUtils) {
- this.participant = participant;
- this.user = user;
- this.header = genericTextHeaderItem;
- this.viewThemeUtils = viewThemeUtils;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof ContactItem inItem) {
- return participant.getCalculatedActorType() == inItem.getModel().getCalculatedActorType() &&
- participant.getCalculatedActorId().equals(inItem.getModel().getCalculatedActorId());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return participant.hashCode();
- }
-
- /**
- * @return the model object
- */
- public Participant getModel() {
- return participant;
- }
-
-
- @Override
- public int getLayoutRes() {
- return R.layout.rv_item_contact;
- }
-
- @Override
- public ContactItemViewHolder createViewHolder(View view, FlexibleAdapter adapter) {
- return new ContactItemViewHolder(view, adapter);
- }
-
- @SuppressLint("SetTextI18n")
- @Override
- public void bindViewHolder(FlexibleAdapter adapter, ContactItemViewHolder holder, int position, List payloads) {
-
- if (participant.getSelected()) {
- viewThemeUtils.platform.colorImageView(holder.binding.checkedImageView);
- holder.binding.checkedImageView.setVisibility(View.VISIBLE);
- } else {
- holder.binding.checkedImageView.setVisibility(View.GONE);
- }
-
- if (!isOnline) {
- holder.binding.nameText.setTextColor(ResourcesCompat.getColor(
- holder.binding.nameText.getContext().getResources(),
- R.color.medium_emphasis_text,
- null)
- );
- holder.binding.avatarView.setAlpha(0.38f);
- } else {
- holder.binding.nameText.setTextColor(ResourcesCompat.getColor(
- holder.binding.nameText.getContext().getResources(),
- R.color.high_emphasis_text,
- null)
- );
- holder.binding.avatarView.setAlpha(1.0f);
- }
-
- holder.binding.nameText.setText(participant.getDisplayName());
-
- if (adapter.hasFilter()) {
- viewThemeUtils.talk.themeAndHighlightText(holder.binding.nameText,
- participant.getDisplayName(),
- String.valueOf(adapter.getFilter(String.class)));
- }
-
- if (TextUtils.isEmpty(participant.getDisplayName()) &&
- (participant.getType() == Participant.ParticipantType.GUEST ||
- participant.getType() == Participant.ParticipantType.USER_FOLLOWING_LINK)) {
- holder.binding.nameText.setText(NextcloudTalkApplication
- .Companion
- .getSharedApplication()
- .getString(R.string.nc_guest));
- }
-
- if (
- participant.getCalculatedActorType() == Participant.ActorType.GROUPS ||
- participant.getCalculatedActorType() == Participant.ActorType.CIRCLES) {
-
- setGenericAvatar(holder, R.drawable.ic_avatar_group, R.drawable.ic_circular_group);
-
- } else if (participant.getCalculatedActorType() == Participant.ActorType.EMAILS) {
-
- setGenericAvatar(holder, R.drawable.ic_avatar_mail, R.drawable.ic_circular_mail);
-
- } else if (
- participant.getCalculatedActorType() == Participant.ActorType.GUESTS ||
- participant.getType() == Participant.ParticipantType.GUEST ||
- participant.getType() == Participant.ParticipantType.GUEST_MODERATOR) {
-
- String displayName;
-
- if (!TextUtils.isEmpty(participant.getDisplayName())) {
- displayName = participant.getDisplayName();
- } else {
- displayName = Objects.requireNonNull(NextcloudTalkApplication.Companion.getSharedApplication())
- .getResources().getString(R.string.nc_guest);
- }
-
- // absolute fallback to prevent NPE deference
- if (displayName == null) {
- displayName = "Guest";
- }
-
- ImageViewExtensionsKt.loadUserAvatar(holder.binding.avatarView, user, displayName, true, false);
- } else if (participant.getCalculatedActorType() == Participant.ActorType.USERS) {
- ImageViewExtensionsKt.loadUserAvatar(holder.binding.avatarView,
- user,
- participant.getCalculatedActorId(),
- true,
- false);
- }
- }
-
- private void setGenericAvatar(
- ContactItemViewHolder holder,
- int roundPlaceholderDrawable,
- int fallbackImageResource) {
- Object avatar;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- avatar = viewThemeUtils.talk.themePlaceholderAvatar(
- holder.binding.avatarView,
- roundPlaceholderDrawable
- );
-
- } else {
- avatar = fallbackImageResource;
- }
-
- ImageViewExtensionsKt.loadUserAvatar(holder.binding.avatarView, avatar);
- }
-
- @Override
- public boolean filter(String constraint) {
- return participant.getDisplayName() != null &&
- (Pattern.compile(constraint, Pattern.CASE_INSENSITIVE | Pattern.LITERAL)
- .matcher(participant.getDisplayName().trim())
- .find() ||
- Pattern.compile(constraint, Pattern.CASE_INSENSITIVE | Pattern.LITERAL)
- .matcher(participant.getCalculatedActorId().trim())
- .find());
- }
-
- @Override
- public GenericTextHeaderItem getHeader() {
- return header;
- }
-
- @Override
- public void setHeader(GenericTextHeaderItem header) {
- this.header = header;
- }
-
- static class ContactItemViewHolder extends FlexibleViewHolder {
-
- RvItemContactBinding binding;
-
- /**
- * Default constructor.
- */
- ContactItemViewHolder(View view, FlexibleAdapter adapter) {
- super(view, adapter);
- binding = RvItemContactBinding.bind(view);
- }
- }
-}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/ContactItem.kt b/app/src/main/java/com/nextcloud/talk/adapters/items/ContactItem.kt
new file mode 100644
index 000000000..e598c6b09
--- /dev/null
+++ b/app/src/main/java/com/nextcloud/talk/adapters/items/ContactItem.kt
@@ -0,0 +1,199 @@
+/*
+ * Nextcloud Talk - Android Client
+ *
+ * SPDX-FileCopyrightText: 2022 Marcel Hibbe
+ * SPDX-FileCopyrightText: 2021 Andy Scherzinger
+ * SPDX-FileCopyrightText: 2017 Mario Danic
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+package com.nextcloud.talk.adapters.items
+
+import android.os.Build
+import android.text.TextUtils
+import android.view.View
+import androidx.core.content.res.ResourcesCompat
+import androidx.recyclerview.widget.RecyclerView
+import com.nextcloud.talk.R
+import com.nextcloud.talk.adapters.items.ContactItem.ContactItemViewHolder
+import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
+import com.nextcloud.talk.data.user.model.User
+import com.nextcloud.talk.databinding.RvItemContactBinding
+import com.nextcloud.talk.extensions.loadUserAvatar
+import com.nextcloud.talk.models.json.participants.Participant
+import com.nextcloud.talk.ui.theme.ViewThemeUtils
+import eu.davidea.flexibleadapter.FlexibleAdapter
+import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
+import eu.davidea.flexibleadapter.items.IFilterable
+import eu.davidea.flexibleadapter.items.IFlexible
+import eu.davidea.flexibleadapter.items.ISectionable
+import eu.davidea.viewholders.FlexibleViewHolder
+import java.util.Objects
+import java.util.regex.Pattern
+
+class ContactItem(
+ /**
+ * @return the model object
+ */
+ val model: Participant,
+ private val user: User,
+ private var header: GenericTextHeaderItem?,
+ private val viewThemeUtils: ViewThemeUtils
+) : AbstractFlexibleItem(),
+ ISectionable,
+ IFilterable {
+ var isOnline: Boolean = true
+
+ override fun equals(o: Any?): Boolean {
+ if (o is ContactItem) {
+ return model.calculatedActorType == o.model.calculatedActorType &&
+ model.calculatedActorId == o.model.calculatedActorId
+ }
+ return false
+ }
+ override fun hashCode(): Int {
+ return model.hashCode()
+ }
+
+ override fun filter(constraint: String?): Boolean {
+ return model.displayName != null &&
+ (
+ Pattern.compile(constraint!!, Pattern.CASE_INSENSITIVE or Pattern.LITERAL)
+ .matcher(model.displayName!!.trim { it <= ' ' })
+ .find() ||
+ Pattern.compile(constraint!!, Pattern.CASE_INSENSITIVE or Pattern.LITERAL)
+ .matcher(model.calculatedActorId!!.trim { it <= ' ' })
+ .find()
+ )
+ }
+
+ override fun getLayoutRes(): Int {
+ return R.layout.rv_item_contact
+ }
+
+ override fun createViewHolder(
+ view: View?,
+ adapter: FlexibleAdapter>?
+ ): ContactItemViewHolder {
+ return ContactItemViewHolder(view, adapter)
+ }
+
+ override fun bindViewHolder(
+ adapter: FlexibleAdapter>?,
+ holder: ContactItemViewHolder?,
+ position: Int,
+ payloads: List?
+ ) {
+ if (model.selected) {
+ holder?.binding?.checkedImageView?.let { viewThemeUtils.platform.colorImageView(it) }
+ holder?.binding?.checkedImageView?.visibility = View.VISIBLE
+ } else {
+ holder?.binding?.checkedImageView?.visibility = View.GONE
+ }
+
+ if (!isOnline) {
+ holder?.binding?.nameText?.setTextColor(
+ ResourcesCompat.getColor(
+ holder.binding.nameText.context.resources,
+ R.color.medium_emphasis_text,
+ null
+ )
+ )
+ holder?.binding?.avatarView?.alpha = 0.38f
+ } else {
+ holder?.binding?.nameText?.setTextColor(
+ ResourcesCompat.getColor(
+ holder.binding.nameText.context.resources,
+ R.color.high_emphasis_text,
+ null
+ )
+ )
+ holder?.binding?.avatarView?.alpha = 1.0f
+ }
+
+ holder?.binding?.nameText?.text = model.displayName
+
+ if (adapter != null) {
+ if (adapter.hasFilter()) {
+ holder?.binding?.let {
+ viewThemeUtils.talk.themeAndHighlightText(
+ it.nameText,
+ model.displayName,
+ adapter.getFilter(String::class.java).toString()
+ )
+ }
+ }
+ }
+
+ if (TextUtils.isEmpty(model.displayName) &&
+ (
+ model.type == Participant.ParticipantType.GUEST ||
+ model.type == Participant.ParticipantType.USER_FOLLOWING_LINK
+ )
+ ) {
+ holder?.binding?.nameText?.text = sharedApplication!!.getString(R.string.nc_guest)
+ }
+
+ if (model.calculatedActorType == Participant.ActorType.GROUPS ||
+ model.calculatedActorType == Participant.ActorType.CIRCLES
+ ) {
+ setGenericAvatar(holder!!, R.drawable.ic_avatar_group, R.drawable.ic_circular_group)
+ } else if (model.calculatedActorType == Participant.ActorType.EMAILS) {
+ setGenericAvatar(holder!!, R.drawable.ic_avatar_mail, R.drawable.ic_circular_mail)
+ } else if (model.calculatedActorType == Participant.ActorType.GUESTS ||
+ model.type == Participant.ParticipantType.GUEST || model.type == Participant.ParticipantType.GUEST_MODERATOR
+ ) {
+ var displayName: String?
+
+ displayName = if (!TextUtils.isEmpty(model.displayName)) {
+ model.displayName
+ } else {
+ Objects.requireNonNull(sharedApplication)!!.resources!!.getString(R.string.nc_guest)
+ }
+
+ // absolute fallback to prevent NPE deference
+ if (displayName == null) {
+ displayName = "Guest"
+ }
+
+ holder?.binding?.avatarView?.loadUserAvatar(user, displayName, true, false)
+ } else if (model.calculatedActorType == Participant.ActorType.USERS) {
+ holder?.binding?.avatarView
+ ?.loadUserAvatar(
+ user,
+ model.calculatedActorId!!,
+ true,
+ false
+ )
+ }
+ }
+
+ private fun setGenericAvatar(
+ holder: ContactItemViewHolder,
+ roundPlaceholderDrawable: Int,
+ fallbackImageResource: Int
+ ) {
+ val avatar = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ viewThemeUtils.talk.themePlaceholderAvatar(
+ holder.binding.avatarView,
+ roundPlaceholderDrawable
+ )
+ } else {
+ fallbackImageResource
+ }
+
+ holder.binding.avatarView.loadUserAvatar(avatar)
+ }
+
+ override fun getHeader(): GenericTextHeaderItem? {
+ return header
+ }
+
+ override fun setHeader(p0: GenericTextHeaderItem?) {
+ this.header = header
+ }
+
+ class ContactItemViewHolder(view: View?, adapter: FlexibleAdapter<*>?) : FlexibleViewHolder(view, adapter) {
+ var binding: RvItemContactBinding =
+ RvItemContactBinding.bind(view!!)
+ }
+}
diff --git a/app/src/main/java/com/nextcloud/talk/adapters/items/GenericTextHeaderItem.java b/app/src/main/java/com/nextcloud/talk/adapters/items/GenericTextHeaderItem.java
deleted file mode 100644
index 34ffc6e18..000000000
--- a/app/src/main/java/com/nextcloud/talk/adapters/items/GenericTextHeaderItem.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Nextcloud Talk - Android Client
- *
- * SPDX-FileCopyrightText: 2022 Andy Scherzinger
- * SPDX-FileCopyrightText: 2017-2018 Mario Danic
- * SPDX-License-Identifier: GPL-3.0-or-later
- */
-package com.nextcloud.talk.adapters.items;
-
-import android.util.Log;
-import android.view.View;
-
-import com.nextcloud.talk.R;
-import com.nextcloud.talk.databinding.RvItemTitleHeaderBinding;
-import com.nextcloud.talk.ui.theme.ViewThemeUtils;
-
-import java.util.List;
-import java.util.Objects;
-
-import eu.davidea.flexibleadapter.FlexibleAdapter;
-import eu.davidea.flexibleadapter.items.AbstractHeaderItem;
-import eu.davidea.flexibleadapter.items.IFlexible;
-import eu.davidea.viewholders.FlexibleViewHolder;
-
-public class GenericTextHeaderItem extends AbstractHeaderItem {
- private static final String TAG = "GenericTextHeaderItem";
-
- private final String title;
- private final ViewThemeUtils viewThemeUtils;
-
- public GenericTextHeaderItem(String title, ViewThemeUtils viewThemeUtils) {
- super();
- setHidden(false);
- setSelectable(false);
- this.title = title;
- this.viewThemeUtils = viewThemeUtils;
- }
-
- public String getModel() {
- return title;
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof GenericTextHeaderItem) {
- GenericTextHeaderItem inItem = (GenericTextHeaderItem) o;
- return title.equals(inItem.getModel());
- }
- return false;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(title);
- }
-
- @Override
- public int getLayoutRes() {
- return R.layout.rv_item_title_header;
- }
-
- @Override
- public void bindViewHolder(FlexibleAdapter adapter, HeaderViewHolder holder, int position, List