Further migration steps

This commit is contained in:
Mario Danic 2019-12-11 13:05:59 +01:00
parent c3a9cca12d
commit 243fa2aad9
No known key found for this signature in database
GPG Key ID: CDE0BBD2738C4CC0
84 changed files with 2600 additions and 3388 deletions

View File

@ -172,6 +172,7 @@ configurations.all {
dependencies {
implementation fileTree(include: ['*'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
// Koin for Android
implementation "org.koin:koin-android:$koin_version"
// Koin AndroidX Scope features
@ -264,11 +265,6 @@ dependencies {
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.10.0.pr1'
kapt 'com.bluelinelabs:logansquare-compiler:1.3.7'
implementation 'com.google.dagger:dagger:2.24'
kapt 'com.google.dagger:dagger-compiler:2.24'
kapt 'com.google.dagger:dagger-android-processor:2.24'
implementation 'com.github.lukaspili.autodagger2:autodagger2:1.1'
kapt 'com.github.lukaspili.autodagger2:autodagger2-compiler:1.1'
compileOnly 'javax.annotation:jsr250-api:1.0'
// Android only
implementation 'org.greenrobot:eventbus:3.1.1'

View File

@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "3cabfd9fea2cd5a25c6c1c8103ef65fb",
"identityHash": "95ddaea30271abd3160e1cdc8ab404d5",
"entities": [
{
"tableName": "conversations",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user` INTEGER, `conversation_id` TEXT, `token` TEXT, `name` TEXT, `display_name` TEXT, `type` INTEGER, `count` INTEGER NOT NULL, `number_of_guests` INTEGER NOT NULL, `participants_count` INTEGER NOT NULL, `participant_type` INTEGER, `has_password` INTEGER NOT NULL, `session_id` TEXT, `favorite` INTEGER NOT NULL, `last_activity` INTEGER NOT NULL, `unread_messages` INTEGER NOT NULL, `unread_mention` INTEGER NOT NULL, `last_message` TEXT, `object_type` TEXT, `notification_level` INTEGER, `read_only_state` INTEGER, `lobby_state` INTEGER, `lobby_timer` INTEGER, `last_read_message` INTEGER NOT NULL, `modified_at` INTEGER, `changing` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`user`) REFERENCES `users`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `user_id` INTEGER, `conversation_id` TEXT, `token` TEXT, `name` TEXT, `display_name` TEXT, `type` INTEGER, `count` INTEGER NOT NULL, `number_of_guests` INTEGER NOT NULL, `participants_count` INTEGER NOT NULL, `participant_type` INTEGER, `has_password` INTEGER NOT NULL, `session_id` TEXT, `favorite` INTEGER NOT NULL, `last_activity` INTEGER NOT NULL, `unread_messages` INTEGER NOT NULL, `unread_mention` INTEGER NOT NULL, `last_message` TEXT, `object_type` TEXT, `notification_level` INTEGER, `read_only_state` INTEGER, `lobby_state` INTEGER, `lobby_timer` INTEGER, `last_read_message` INTEGER NOT NULL, `modified_at` INTEGER, `changing` INTEGER NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`user_id`) REFERENCES `users`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "id",
@ -16,7 +16,7 @@
},
{
"fieldPath": "user",
"columnName": "user",
"columnName": "user_id",
"affinity": "INTEGER",
"notNull": false
},
@ -173,13 +173,13 @@
},
"indices": [
{
"name": "index_conversations_user_conversation_id",
"name": "index_conversations_user_id_token",
"unique": true,
"columnNames": [
"user",
"conversation_id"
"user_id",
"token"
],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_conversations_user_conversation_id` ON `${TABLE_NAME}` (`user`, `conversation_id`)"
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_conversations_user_id_token` ON `${TABLE_NAME}` (`user_id`, `token`)"
}
],
"foreignKeys": [
@ -188,7 +188,7 @@
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"user"
"user_id"
],
"referencedColumns": [
"id"
@ -198,7 +198,7 @@
},
{
"tableName": "messages",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `conversation` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `actor_id` TEXT, `actor_type` TEXT, `actor_display_name` TEXT, `timestamp` INTEGER NOT NULL, `message` TEXT, `system_message_type` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`conversation`) REFERENCES `conversations`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `conversation_id` TEXT NOT NULL, `message_id` INTEGER NOT NULL, `actor_id` TEXT, `actor_type` TEXT, `actor_display_name` TEXT, `timestamp` INTEGER NOT NULL, `message` TEXT, `system_message_type` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`conversation_id`) REFERENCES `conversations`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)",
"fields": [
{
"fieldPath": "id",
@ -208,7 +208,7 @@
},
{
"fieldPath": "conversation",
"columnName": "conversation",
"columnName": "conversation_id",
"affinity": "TEXT",
"notNull": true
},
@ -261,23 +261,14 @@
],
"autoGenerate": false
},
"indices": [
{
"name": "index_messages_conversation",
"unique": false,
"columnNames": [
"conversation"
],
"createSql": "CREATE INDEX IF NOT EXISTS `index_messages_conversation` ON `${TABLE_NAME}` (`conversation`)"
}
],
"indices": [],
"foreignKeys": [
{
"table": "conversations",
"onDelete": "CASCADE",
"onUpdate": "CASCADE",
"columns": [
"conversation"
"conversation_id"
],
"referencedColumns": [
"id"
@ -369,7 +360,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3cabfd9fea2cd5a25c6c1c8103ef65fb')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '95ddaea30271abd3160e1cdc8ab404d5')"
]
}
}

View File

@ -21,6 +21,7 @@
package com.nextcloud.talk;
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;

View File

@ -1,73 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.services.firebase;
import android.annotation.SuppressLint;
import autodagger.AutoInjector;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.jobs.NotificationWorker;
import com.nextcloud.talk.jobs.PushRegistrationWorker;
import com.nextcloud.talk.utils.bundle.BundleKeys;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import javax.inject.Inject;
@AutoInjector(NextcloudTalkApplication.class)
public class MagicFirebaseMessagingService extends FirebaseMessagingService {
@Inject
AppPreferences appPreferences;
@Override
public void onNewToken(String token) {
super.onNewToken(token);
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
appPreferences.setPushToken(token);
OneTimeWorkRequest pushRegistrationWork = new OneTimeWorkRequest.Builder(PushRegistrationWorker.class).build();
WorkManager.getInstance().enqueue(pushRegistrationWork);
}
@SuppressLint("LongLogTag")
@Override
public void onMessageReceived(RemoteMessage remoteMessage) {
if (remoteMessage == null) {
return;
}
if (remoteMessage.getData() != null) {
Data messageData = new Data.Builder()
.putString(BundleKeys.INSTANCE.getKEY_NOTIFICATION_SUBJECT(), remoteMessage.getData().get("subject"))
.putString(BundleKeys.INSTANCE.getKEY_NOTIFICATION_SIGNATURE(), remoteMessage.getData().get("signature"))
.build();
OneTimeWorkRequest pushNotificationWork = new OneTimeWorkRequest.Builder(NotificationWorker.class)
.setInputData(messageData)
.build();
WorkManager.getInstance().enqueue(pushNotificationWork);
}
}
}

View File

@ -0,0 +1,63 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.services.firebase
import android.annotation.SuppressLint
import autodagger.AutoInjector
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.jobs.NotificationWorker
import com.nextcloud.talk.jobs.PushRegistrationWorker
import com.nextcloud.talk.utils.bundle.BundleKeys
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.nextcloud.talk.utils.preferences.AppPreferences
class MagicFirebaseMessagingService : FirebaseMessagingService(), KoinComponent {
val appPreferences: AppPreferences by inject()
@Override
fun onNewToken(token: String?) {
super.onNewToken(token)
appPreferences.setPushToken(token)
val pushRegistrationWork: OneTimeWorkRequest = Builder(PushRegistrationWorker::class.java).build()
WorkManager.getInstance().enqueue(pushRegistrationWork)
}
@SuppressLint("LongLogTag")
@Override
fun onMessageReceived(remoteMessage: RemoteMessage?) {
if (remoteMessage == null) {
return
}
if (remoteMessage.getData() != null) {
val messageData: Data = Builder()
.putString(BundleKeys.INSTANCE.getKEY_NOTIFICATION_SUBJECT(), remoteMessage.getData().get("subject"))
.putString(BundleKeys.INSTANCE.getKEY_NOTIFICATION_SIGNATURE(), remoteMessage.getData().get("signature"))
.build()
val pushNotificationWork: OneTimeWorkRequest = Builder(NotificationWorker::class.java)
.setInputData(messageData)
.build()
WorkManager.getInstance().enqueue(pushNotificationWork)
}
}
}

View File

@ -28,7 +28,6 @@ import android.util.Log
import android.view.WindowManager
import android.webkit.SslErrorHandler
import androidx.appcompat.app.AppCompatActivity
import autodagger.AutoInjector
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.events.CertificateEvent
@ -39,25 +38,18 @@ import com.yarolegovich.lovelydialog.LovelyStandardDialog
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.koin.android.ext.android.inject
import java.security.cert.CertificateParsingException
import java.security.cert.X509Certificate
import java.text.DateFormat
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
open class BaseActivity : AppCompatActivity() {
@Inject
lateinit var eventBus: EventBus
@Inject
lateinit var appPreferences: AppPreferences
@Inject
lateinit var context: Context
val eventBus: EventBus by inject()
val appPreferences: AppPreferences by inject()
val context: Context by inject()
override fun onCreate(savedInstanceState: Bundle?) {
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
super.onCreate(savedInstanceState)
}

View File

@ -27,7 +27,6 @@ import android.os.Build
import android.os.Bundle
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import autodagger.AutoInjector
import butterknife.BindView
import butterknife.ButterKnife
import com.bluelinelabs.conductor.Conductor
@ -37,7 +36,6 @@ import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
import com.google.android.material.appbar.MaterialToolbar
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.controllers.CallNotificationController
import com.nextcloud.talk.controllers.LockedController
import com.nextcloud.talk.controllers.ServerSelectionController
@ -47,16 +45,11 @@ import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListVi
import com.nextcloud.talk.utils.ConductorRemapping
import com.nextcloud.talk.utils.SecurityUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.database.user.UserUtils
import io.requery.Persistable
import io.requery.android.sqlcipher.SqlCipherDatabaseSource
import io.requery.reactivex.ReactiveEntityStore
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class MainActivity : BaseActivity(), ActionBarProvider {
@BindView(R.id.toolbar)
@ -65,9 +58,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
lateinit var container: ViewGroup
val usersRepository: UsersRepository by inject()
@Inject
lateinit var sqlCipherDatabaseSource: SqlCipherDatabaseSource
val sqlCipherDatabaseSource: SqlCipherDatabaseSource by inject()
private var router: Router? = null
@ -76,7 +67,6 @@ class MainActivity : BaseActivity(), ActionBarProvider {
setContentView(R.layout.activity_main)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
ButterKnife.bind(this)
setSupportActionBar(toolbar)

View File

@ -22,11 +22,7 @@ package com.nextcloud.talk.adapters.items
import android.accounts.Account
import android.view.View
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.RelativeLayout
import android.widget.TextView
import android.widget.*
import androidx.emoji.widget.EmojiTextView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import butterknife.BindView
@ -35,8 +31,8 @@ import coil.api.load
import coil.transform.CircleCropTransformation
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.utils.ApiUtils
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
@ -52,7 +48,7 @@ class AdvancedUserItem(
*/
val model: Participant,
val entity: UserEntity?,
val entity: UserNgEntity?,
val account: Account?
) : AbstractFlexibleItem<AdvancedUserItem.UserItemViewHolder>(), IFilterable<String> {
override fun bindViewHolder(

View File

@ -32,7 +32,6 @@ import coil.api.load
import coil.transform.CircleCropTransformation
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.converters.EnumParticipantTypeConverter
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.newarch.local.models.UserNgEntity

View File

@ -33,7 +33,6 @@ import android.view.View
import android.widget.TextView
import androidx.core.view.ViewCompat
import androidx.emoji.widget.EmojiTextView
import autodagger.AutoInjector
import butterknife.BindView
import butterknife.ButterKnife
import com.amulyakhare.textdrawable.TextDrawable
@ -48,9 +47,7 @@ import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders
import org.koin.core.KoinComponent
import org.koin.core.inject
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class MagicIncomingTextMessageViewHolder(incomingView: View) : MessageHolders
.IncomingTextMessageViewHolder<ChatMessage>(incomingView), KoinComponent {
@ -70,9 +67,7 @@ class MagicIncomingTextMessageViewHolder(incomingView: View) : MessageHolders
@BindView(R.id.messageTime)
var messageTimeView: TextView? = null
@JvmField
@Inject
var context: Context? = null
val context: Context by inject()
val appPreferences: AppPreferences by inject()
@ -81,9 +76,6 @@ class MagicIncomingTextMessageViewHolder(incomingView: View) : MessageHolders
this,
itemView
)
NextcloudTalkApplication.sharedApplication!!
.componentApplication
.inject(this)
}
override fun onBind(message: ChatMessage) {

View File

@ -1,146 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.adapters.messages;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.Spannable;
import android.text.SpannableString;
import android.util.TypedValue;
import android.view.View;
import android.widget.TextView;
import androidx.core.view.ViewCompat;
import androidx.emoji.widget.EmojiTextView;
import autodagger.AutoInjector;
import butterknife.BindView;
import butterknife.ButterKnife;
import com.google.android.flexbox.FlexboxLayout;
import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.models.json.chat.ChatMessage;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.TextMatchers;
import com.nextcloud.talk.utils.database.user.UserUtils;
import com.stfalcon.chatkit.messages.MessageHolders;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
@AutoInjector(NextcloudTalkApplication.class)
public class MagicOutcomingTextMessageViewHolder
extends MessageHolders.OutcomingTextMessageViewHolder<ChatMessage> {
@BindView(R.id.messageText)
EmojiTextView messageText;
@BindView(R.id.messageTime)
TextView messageTimeView;
@Inject
UserUtils userUtils;
@Inject
Context context;
private View itemView;
public MagicOutcomingTextMessageViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
this.itemView = itemView;
}
@Override
public void onBind(ChatMessage message) {
super.onBind(message);
HashMap<String, HashMap<String, String>> messageParameters = message.getMessageParameters();
Spannable messageString = new SpannableString(message.getText());
itemView.setSelected(false);
messageTimeView.setTextColor(context.getResources().getColor(R.color.white60));
FlexboxLayout.LayoutParams layoutParams =
(FlexboxLayout.LayoutParams) messageTimeView.getLayoutParams();
layoutParams.setWrapBefore(false);
float textSize = context.getResources().getDimension(R.dimen.chat_text_size);
if (messageParameters != null && messageParameters.size() > 0) {
for (String key : messageParameters.keySet()) {
Map<String, String> individualHashMap = message.getMessageParameters().get(key);
if (individualHashMap != null) {
if (individualHashMap.get("type").equals("user") || individualHashMap.get("type")
.equals("guest") || individualHashMap.get("type").equals("call")) {
messageString =
DisplayUtils.INSTANCE.searchAndReplaceWithMentionSpan(messageText.getContext(),
messageString,
individualHashMap.get("id"),
individualHashMap.get("name"),
individualHashMap.get("type"),
message.activeUser,
R.xml.chip_others);
} else if (individualHashMap.get("type").equals("file")) {
itemView.setOnClickListener(v -> {
Intent browserIntent =
new Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap.get("link")));
context.startActivity(browserIntent);
});
}
}
}
} else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.getText())) {
textSize = (float) (textSize * 2.5);
layoutParams.setWrapBefore(true);
messageTimeView.setTextColor(context.getResources().getColor(R.color.warm_grey_four));
itemView.setSelected(true);
}
Resources resources = NextcloudTalkApplication.Companion.getSharedApplication().getResources();
if (message.grouped) {
Drawable bubbleDrawable =
DisplayUtils.INSTANCE.getMessageSelector(
resources.getColor(R.color.bg_message_list_outcoming_bubble),
resources.getColor(R.color.transparent),
resources.getColor(R.color.bg_message_list_outcoming_bubble),
R.drawable.shape_grouped_outcoming_message);
ViewCompat.setBackground(bubble, bubbleDrawable);
} else {
Drawable bubbleDrawable = DisplayUtils.INSTANCE.getMessageSelector(
resources.getColor(R.color.bg_message_list_outcoming_bubble),
resources.getColor(R.color.transparent),
resources.getColor(R.color.bg_message_list_outcoming_bubble),
R.drawable.shape_outcoming_message);
ViewCompat.setBackground(bubble, bubbleDrawable);
}
messageText.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
messageTimeView.setLayoutParams(layoutParams);
messageText.setText(messageString);
}
}

View File

@ -0,0 +1,118 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.adapters.messages
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.text.Spannable
import android.text.SpannableString
import android.util.TypedValue
import android.view.View
import android.widget.TextView
import androidx.core.view.ViewCompat
import androidx.emoji.widget.EmojiTextView
import butterknife.BindView
import butterknife.ButterKnife
import com.google.android.flexbox.FlexboxLayout
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.utils.DisplayUtils.getMessageSelector
import com.nextcloud.talk.utils.DisplayUtils.searchAndReplaceWithMentionSpan
import com.nextcloud.talk.utils.TextMatchers
import com.nextcloud.talk.utils.database.user.UserUtils
import com.stfalcon.chatkit.messages.MessageHolders.OutcomingTextMessageViewHolder
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.*
class MagicOutcomingTextMessageViewHolder(itemView: View) : OutcomingTextMessageViewHolder<ChatMessage>(itemView), KoinComponent {
@JvmField
@BindView(R.id.messageText)
var messageText: EmojiTextView? = null
@JvmField
@BindView(R.id.messageTime)
var messageTimeView: TextView? = null
val userUtils: UserUtils by inject()
val context: Context by inject()
private val realView: View
override fun onBind(message: ChatMessage) {
super.onBind(message)
val messageParameters: HashMap<String, HashMap<String, String>> = message.messageParameters
var messageString: Spannable = SpannableString(message.text)
realView.setSelected(false)
messageTimeView!!.setTextColor(context!!.resources.getColor(R.color.white60))
val layoutParams = messageTimeView!!.layoutParams as FlexboxLayout.LayoutParams
layoutParams.isWrapBefore = false
var textSize = context!!.resources.getDimension(R.dimen.chat_text_size)
if (messageParameters != null && messageParameters.size > 0) {
for (key in messageParameters.keys) {
val individualHashMap: HashMap<String, String>? = message.messageParameters[key]
if (individualHashMap != null) {
if (individualHashMap["type"] == "user" || (individualHashMap["type"]
== "guest") || individualHashMap["type"] == "call") {
messageString = searchAndReplaceWithMentionSpan(messageText!!.context,
messageString,
individualHashMap["id"]!!,
individualHashMap["name"]!!,
individualHashMap["type"]!!,
message.activeUser,
R.xml.chip_others)
} else if (individualHashMap["type"] == "file") {
realView.setOnClickListener(View.OnClickListener { v: View? ->
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap["link"]))
context!!.startActivity(browserIntent)
})
}
}
}
} else if (TextMatchers.isMessageWithSingleEmoticonOnly(message.text)) {
textSize = (textSize * 2.5).toFloat()
layoutParams.isWrapBefore = true
messageTimeView!!.setTextColor(context!!.resources.getColor(R.color.warm_grey_four))
realView.setSelected(true)
}
val resources = sharedApplication!!.resources
if (message.grouped) {
val bubbleDrawable = getMessageSelector(
resources.getColor(R.color.bg_message_list_outcoming_bubble),
resources.getColor(R.color.transparent),
resources.getColor(R.color.bg_message_list_outcoming_bubble),
R.drawable.shape_grouped_outcoming_message)
ViewCompat.setBackground(bubble, bubbleDrawable)
} else {
val bubbleDrawable = getMessageSelector(
resources.getColor(R.color.bg_message_list_outcoming_bubble),
resources.getColor(R.color.transparent),
resources.getColor(R.color.bg_message_list_outcoming_bubble),
R.drawable.shape_outcoming_message)
ViewCompat.setBackground(bubble, bubbleDrawable)
}
messageText!!.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
messageTimeView!!.layoutParams = layoutParams
messageText!!.text = messageString
}
init {
ButterKnife.bind(this, itemView)
this.realView = itemView
}
}

View File

@ -30,24 +30,16 @@ import android.net.Uri
import android.os.Handler
import android.view.View
import androidx.emoji.widget.EmojiTextView
import autodagger.AutoInjector
import butterknife.BindView
import butterknife.ButterKnife
import coil.api.load
import coil.transform.CircleCropTransformation
import com.nextcloud.talk.R.drawable
import com.nextcloud.talk.R.id
import com.nextcloud.talk.R.string
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.R.*
import com.nextcloud.talk.components.filebrowser.models.BrowserFile
import com.nextcloud.talk.components.filebrowser.models.DavResponse
import com.nextcloud.talk.components.filebrowser.webdav.ReadFilesystemOperation
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.SINGLE_LINK_GIPHY_MESSAGE
import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.SINGLE_LINK_IMAGE_MESSAGE
import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.SINGLE_LINK_TENOR_MESSAGE
import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE
import com.nextcloud.talk.models.json.chat.ChatMessage.MessageType.*
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.utils.AccountUtils.canWeOpenFilesApp
import com.nextcloud.talk.utils.DisplayUtils.setClickableString
@ -60,21 +52,17 @@ import io.reactivex.SingleObserver
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.OkHttpClient
import javax.inject.Inject
import org.koin.core.KoinComponent
import org.koin.core.inject
@AutoInjector(NextcloudTalkApplication::class)
class MagicPreviewMessageViewHolder(itemView: View?) : IncomingImageMessageViewHolder<ChatMessage>(
itemView
) {
), KoinComponent {
@JvmField
@BindView(id.messageText)
var messageText: EmojiTextView? = null
@JvmField
@Inject
var context: Context? = null
@JvmField
@Inject
var okHttpClient: OkHttpClient? = null
val context: Context by inject()
val okHttpClient: OkHttpClient by inject()
@SuppressLint("SetTextI18n")
override fun onBind(message: ChatMessage) {
@ -223,6 +211,5 @@ class MagicPreviewMessageViewHolder(itemView: View?) : IncomingImageMessageViewH
init {
ButterKnife.bind(this, itemView!!)
sharedApplication!!.componentApplication.inject(this)
}
}

View File

@ -1,91 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.adapters.messages;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.text.Spannable;
import android.text.SpannableString;
import android.view.View;
import androidx.core.view.ViewCompat;
import autodagger.AutoInjector;
import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.models.json.chat.ChatMessage;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.stfalcon.chatkit.messages.MessageHolders;
import java.util.Map;
import javax.inject.Inject;
@AutoInjector(NextcloudTalkApplication.class)
public class MagicSystemMessageViewHolder
extends MessageHolders.IncomingTextMessageViewHolder<ChatMessage> {
@Inject
AppPreferences appPreferences;
@Inject
Context context;
public MagicSystemMessageViewHolder(View itemView) {
super(itemView);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
}
@Override
public void onBind(ChatMessage message) {
super.onBind(message);
Resources resources = itemView.getResources();
int normalColor = resources.getColor(R.color.bg_message_list_incoming_bubble);
int pressedColor;
int mentionColor;
pressedColor = normalColor;
mentionColor = resources.getColor(R.color.nc_author_text);
Drawable bubbleDrawable = DisplayUtils.INSTANCE.getMessageSelector(normalColor,
resources.getColor(R.color.transparent), pressedColor,
R.drawable.shape_grouped_incoming_message);
ViewCompat.setBackground(bubble, bubbleDrawable);
Spannable messageString = new SpannableString(message.getText());
if (message.getMessageParameters() != null && message.getMessageParameters().size() > 0) {
for (String key : message.getMessageParameters().keySet()) {
Map<String, String> individualHashMap = message.getMessageParameters().get(key);
if (individualHashMap != null && (individualHashMap.get("type").equals("user")
|| individualHashMap.get("type").equals("guest")
|| individualHashMap.get("type").equals("call"))) {
messageString =
DisplayUtils.INSTANCE.searchAndColor(messageString, "@" + individualHashMap.get("name"),
mentionColor);
}
}
}
text.setText(messageString);
}
}

View File

@ -0,0 +1,65 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.adapters.messages
import android.content.Context
import android.text.Spannable
import android.text.SpannableString
import android.view.View
import androidx.core.view.ViewCompat
import com.nextcloud.talk.R
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.utils.DisplayUtils.getMessageSelector
import com.nextcloud.talk.utils.DisplayUtils.searchAndColor
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.stfalcon.chatkit.messages.MessageHolders.IncomingTextMessageViewHolder
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.*
class MagicSystemMessageViewHolder(itemView: View?) : IncomingTextMessageViewHolder<ChatMessage>(itemView), KoinComponent {
val appPreferences: AppPreferences by inject()
val context: Context by inject()
override fun onBind(message: ChatMessage) {
super.onBind(message)
val resources = itemView.resources
val normalColor = resources.getColor(R.color.bg_message_list_incoming_bubble)
val pressedColor: Int
val mentionColor: Int
pressedColor = normalColor
mentionColor = resources.getColor(R.color.nc_author_text)
val bubbleDrawable = getMessageSelector(normalColor,
resources.getColor(R.color.transparent), pressedColor,
R.drawable.shape_grouped_incoming_message)
ViewCompat.setBackground(bubble, bubbleDrawable)
var messageString: Spannable = SpannableString(message.text)
if (message.messageParameters != null && message.messageParameters.size > 0) {
for (key in message.messageParameters.keys) {
val individualHashMap: HashMap<String, String>? = message.messageParameters[key]
if (individualHashMap != null && (individualHashMap["type"] == "user" || individualHashMap["type"] == "guest" || individualHashMap["type"] == "call")) {
messageString = searchAndColor(messageString, "@" + individualHashMap["name"],
mentionColor)
}
}
}
text.text = messageString
}
}

View File

@ -33,8 +33,6 @@ import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import autodagger.AutoComponent
import autodagger.AutoInjector
import coil.Coil
import coil.ImageLoader
import com.bluelinelabs.logansquare.LoganSquare
@ -43,10 +41,6 @@ import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.core.ImagePipelineConfig
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.components.filebrowser.webdav.DavUtils
import com.nextcloud.talk.dagger.modules.BusModule
import com.nextcloud.talk.dagger.modules.ContextModule
import com.nextcloud.talk.dagger.modules.DatabaseModule
import com.nextcloud.talk.dagger.modules.RestModule
import com.nextcloud.talk.jobs.AccountRemovalWorker
import com.nextcloud.talk.jobs.CapabilitiesWorker
import com.nextcloud.talk.jobs.PushRegistrationWorker
@ -61,14 +55,10 @@ import com.nextcloud.talk.newarch.di.module.StorageModule
import com.nextcloud.talk.newarch.features.conversationsList.di.module.ConversationsListModule
import com.nextcloud.talk.newarch.local.dao.UsersDao
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.other.UserStatus.ACTIVE
import com.nextcloud.talk.newarch.local.models.other.UserStatus.DORMANT
import com.nextcloud.talk.newarch.local.models.other.UserStatus.PENDING_DELETE
import com.nextcloud.talk.newarch.local.models.other.UserStatus.*
import com.nextcloud.talk.utils.ClosedInterfaceImpl
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.OkHttpNetworkFetcherWithCache
import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageModule
import com.nextcloud.talk.utils.database.user.UserModule
import com.nextcloud.talk.utils.database.user.UserUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.nextcloud.talk.webrtc.MagicWebRTCUtils
@ -89,24 +79,11 @@ import org.webrtc.voiceengine.WebRtcAudioManager
import org.webrtc.voiceengine.WebRtcAudioUtils
import java.security.Security
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
@AutoComponent(
modules = [BusModule::class, ContextModule::class, DatabaseModule::class, RestModule::class, UserModule::class, ArbitraryStorageModule::class]
)
@Singleton
@AutoInjector(NextcloudTalkApplication::class)
class NextcloudTalkApplication : Application(), LifecycleObserver {
//region Fields (components)
lateinit var componentApplication: NextcloudTalkApplicationComponent
private set
//endregion
//region Getters
@Inject
lateinit var userUtils: UserUtils
val userUtils: UserUtils by inject()
val imageLoader: ImageLoader by inject()
val appPreferences: AppPreferences by inject()
val okHttpClient: OkHttpClient by inject()
@ -151,10 +128,9 @@ class NextcloudTalkApplication : Application(), LifecycleObserver {
initializeWebRtc()
DisplayUtils.useCompatVectorIfNeeded()
buildComponent()
startKoin()
DavUtils.registerCustomFactories()
componentApplication.inject(this)
Coil.setDefaultImageLoader(imageLoader)
migrateUsers()
@ -219,16 +195,7 @@ class NextcloudTalkApplication : Application(), LifecycleObserver {
//endregion
//region Protected methods
protected fun buildComponent() {
componentApplication = DaggerNextcloudTalkApplicationComponent.builder()
.busModule(BusModule())
.contextModule(ContextModule(applicationContext))
.databaseModule(DatabaseModule())
.restModule(RestModule(applicationContext))
.userModule(UserModule())
.arbitraryStorageModule(ArbitraryStorageModule())
.build()
protected fun startKoin() {
startKoin {
androidContext(this@NextcloudTalkApplication)
androidLogger()

View File

@ -26,7 +26,6 @@ import android.view.View
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import autodagger.AutoInjector
import butterknife.BindView
import butterknife.ButterKnife
import coil.api.load
@ -34,7 +33,6 @@ import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.components.filebrowser.models.BrowserFile
import com.nextcloud.talk.interfaces.SelectionInterface
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.utils.getCredentials
@ -46,25 +44,17 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFilterable
import eu.davidea.flexibleadapter.items.IFlexible
import eu.davidea.viewholders.FlexibleViewHolder
import javax.inject.Inject
import org.koin.core.KoinComponent
import org.koin.core.inject
@AutoInjector(NextcloudTalkApplication::class)
class BrowserFileItem(
val model: BrowserFile,
private val activeUser: UserNgEntity,
private val selectionInterface: SelectionInterface
) : AbstractFlexibleItem<BrowserFileItem.ViewHolder>(), IFilterable<String> {
@JvmField
@Inject
var context: Context? = null
) : AbstractFlexibleItem<BrowserFileItem.ViewHolder>(), IFilterable<String>, KoinComponent {
val context: Context by inject()
var isSelected: Boolean = false
init {
NextcloudTalkApplication.sharedApplication!!
.componentApplication
.inject(this)
}
override fun equals(other: Any?): Boolean {
if (other is BrowserFileItem) {
val inItem = other as BrowserFileItem?

View File

@ -70,7 +70,6 @@ import okhttp3.OkHttpClient;
import org.parceler.Parcel;
import org.parceler.Parcels;
@AutoInjector(NextcloudTalkApplication.class)
public class BrowserController extends BaseController implements ListingInterface,
FlexibleAdapter.OnItemClickListener, SelectionInterface {
private final Set<String> selectedPaths;
@ -83,8 +82,6 @@ public class BrowserController extends BaseController implements ListingInterfac
@BindView(R.id.action_refresh)
BottomNavigationItemView actionRefreshMenuItem;
@Inject
Context context;
@Inject
OkHttpClient okHttpClient;
private MenuItem filesSelectionDoneMenuItem;
@ -102,9 +99,6 @@ public class BrowserController extends BaseController implements ListingInterfac
public BrowserController(Bundle args) {
super();
setHasOptionsMenu(true);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
browserType = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_BROWSER_TYPE()));
activeUser = Parcels.unwrap(args.getParcelable(BundleKeys.INSTANCE.getKEY_USER_ENTITY()));
roomToken = args.getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN());

View File

@ -20,18 +20,19 @@
package com.nextcloud.talk.components.filebrowser.webdav;
import at.bitfire.dav4jvm.DavResource;
import at.bitfire.dav4jvm.Response;
import at.bitfire.dav4jvm.exception.DavException;
import com.nextcloud.talk.components.filebrowser.models.BrowserFile;
import com.nextcloud.talk.components.filebrowser.models.DavResponse;
import com.nextcloud.talk.dagger.modules.RestModule;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
import com.nextcloud.talk.utils.ApiUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import at.bitfire.dav4jvm.DavResource;
import at.bitfire.dav4jvm.Response;
import at.bitfire.dav4jvm.exception.DavException;
import kotlin.Unit;
import kotlin.jvm.functions.Function2;
import okhttp3.HttpUrl;
@ -68,7 +69,7 @@ public class ReadFilesystemOperation {
new Function2<Response, Response.HrefRelation, Unit>() {
@Override
public Unit invoke(Response response, Response.HrefRelation hrefRelation) {
davResponse.setResponse(response);
davResponse.data = response;
switch (hrefRelation) {
case MEMBER:
memberElements.add(response);
@ -95,7 +96,7 @@ public class ReadFilesystemOperation {
memberElement.getHref().toString().substring(basePath.length())));
}
davResponse.setData(remoteFiles);
davResponse.data = remoteFiles;
return davResponse;
}
}

View File

@ -31,7 +31,6 @@ import android.widget.TextView
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import autodagger.AutoInjector
import butterknife.BindView
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
@ -67,14 +66,10 @@ import org.greenrobot.eventbus.ThreadMode
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.net.CookieManager
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class AccountVerificationController(args: Bundle?) : BaseController(), KoinComponent {
@JvmField @Inject
internal var ncApi: NcApi? = null
val ncApi: NcApi by inject()
val cookieManager: CookieManager by inject()
val usersRepository: UsersRepository by inject()
val usersDao: UsersDao by inject()
@ -120,10 +115,6 @@ class AccountVerificationController(args: Bundle?) : BaseController(), KoinCompo
override fun onViewBound(view: View) {
super.onViewBound(view)
NextcloudTalkApplication.sharedApplication!!
.componentApplication
.inject(this)
if (activity != null) {
activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}

View File

@ -141,7 +141,6 @@ import org.webrtc.VideoSource
import org.webrtc.VideoTrack
import pub.devrel.easypermissions.AfterPermissionGranted
@AutoInjector(NextcloudTalkApplication::class)
class CallController(args: Bundle) : BaseController() {
@JvmField
@ -189,13 +188,8 @@ class CallController(args: Bundle) : BaseController() {
@BindView(R.id.progress_bar)
var progressBar: ProgressBar? = null
@JvmField
@Inject
var ncApi: NcApi? = null
@JvmField
@Inject
var userUtils: UserUtils? = null
val userUtils: UserUtils by inject()
val ncApi: NcApi by inject()
val cookieManager: CookieManager by inject()
private var peerConnectionFactory: PeerConnectionFactory? = null
private var audioConstraints: MediaConstraints? = null
@ -259,10 +253,6 @@ class CallController(args: Bundle) : BaseController() {
get() = currentCallStatus == CallStatus.ESTABLISHED || currentCallStatus == CallStatus.IN_CONVERSATION
init {
NextcloudTalkApplication.sharedApplication!!
.componentApplication
.inject(this)
roomId = args.getString(BundleKeys.KEY_ROOM_ID, "")
roomToken = args.getString(BundleKeys.KEY_ROOM_TOKEN, "")
conversationUser = args.getParcelable(BundleKeys.KEY_USER_ENTITY)

View File

@ -42,7 +42,6 @@ import android.widget.ImageView
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import autodagger.AutoInjector
import butterknife.BindView
import butterknife.OnClick
import coil.api.load
@ -79,21 +78,15 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.koin.android.ext.android.inject
import org.michaelevans.colorart.library.ColorArt
import org.parceler.Parcels
import java.io.IOException
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class CallNotificationController(private val originalBundle: Bundle) : BaseController() {
@JvmField
@Inject
internal var ncApi: NcApi? = null
@JvmField
@Inject
internal var arbitraryStorageUtils: ArbitraryStorageUtils? = null
val ncApi: NcApi by inject()
val arbitraryStorageUtils: ArbitraryStorageUtils by inject()
@JvmField
@BindView(R.id.conversationNameTextView)
@ -128,10 +121,6 @@ class CallNotificationController(private val originalBundle: Bundle) : BaseContr
private var handler: Handler? = null
init {
NextcloudTalkApplication.sharedApplication!!
.componentApplication
.inject(this)
this.roomId = originalBundle.getString(BundleKeys.KEY_ROOM_ID, "")
this.currentConversation = Parcels.unwrap(originalBundle.getParcelable(BundleKeys.KEY_ROOM))
this.userBeingCalled = originalBundle.getParcelable(BundleKeys.KEY_USER_ENTITY)
@ -296,7 +285,7 @@ class CallNotificationController(private val originalBundle: Bundle) : BaseContr
}
var importantConversation = false
val arbitraryStorageEntity: ArbitraryStorageEntity? = arbitraryStorageUtils!!.getStorageSetting(
val arbitraryStorageEntity: ArbitraryStorageEntity? = arbitraryStorageUtils.getStorageSetting(
userBeingCalled!!.id,
"important_conversation",
currentConversation!!.token

View File

@ -49,7 +49,6 @@ import androidx.emoji.text.EmojiCompat
import androidx.emoji.widget.EmojiEditText
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import autodagger.AutoInjector
import butterknife.BindView
import butterknife.OnClick
import coil.api.load
@ -123,19 +122,14 @@ import java.util.Date
import java.util.HashMap
import java.util.Objects
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
.OnLoadMoreListener, MessagesListAdapter.Formatter<Date>, MessagesListAdapter
.OnMessageLongClickListener<IMessage>, MessageHolders.ContentChecker<IMessage> {
@Inject
@JvmField
var ncApi: NcApi? = null
@Inject
@JvmField
var userUtils: UserUtils? = null
val ncApi: NcApi by inject()
val userUtils: UserUtils by inject()
@BindView(R.id.messagesListView)
@JvmField
var messagesListView: MessagesList? = null
@ -199,7 +193,6 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
init {
setHasOptionsMenu(true)
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
this.conversationUser = args.getParcelable(BundleKeys.KEY_USER_ENTITY)
this.roomId = args.getString(BundleKeys.KEY_ROOM_ID, "")
@ -1517,7 +1510,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
companion object {
private val TAG = "ChatController"
private val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1
private val CONTENT_TYPE_UNREAD_NOTICE_MESSAGE: Byte = 2
val CONTENT_TYPE_SYSTEM_MESSAGE: Byte = 1
val CONTENT_TYPE_UNREAD_NOTICE_MESSAGE: Byte = 2
}
}

View File

@ -101,7 +101,6 @@ import java.util.HashMap
import java.util.HashSet
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class ContactsController : BaseController,
SearchView.OnQueryTextListener,
FlexibleAdapter.OnItemClickListener,
@ -109,7 +108,7 @@ class ContactsController : BaseController,
FlexibleAdapter.EndlessScrollListener {
val usersRepository: UsersRepository by inject()
val ncApi: NcApi by inject()
@JvmField
@BindView(R.id.initial_relative_layout)
var initialRelativeLayout: RelativeLayout? = null
@ -143,9 +142,6 @@ class ContactsController : BaseController,
@BindView(R.id.generic_rv_layout)
var genericRvLayout: CoordinatorLayout? = null
@JvmField
@Inject
var ncApi: NcApi? = null
private var credentials: String? = null
private var currentUser: UserNgEntity? = null
private var adapter: FlexibleAdapter<AbstractFlexibleItem<*>>? = null

View File

@ -1,179 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.controllers;
import android.app.Activity;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.biometric.BiometricPrompt;
import androidx.fragment.app.FragmentActivity;
import autodagger.AutoInjector;
import butterknife.OnClick;
import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.controllers.base.BaseController;
import com.nextcloud.talk.utils.SecurityUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import javax.inject.Inject;
@AutoInjector(NextcloudTalkApplication.class)
public class LockedController extends BaseController {
public static final String TAG = "LockedController";
private static final int REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 112;
@Inject
AppPreferences appPreferences;
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_locked, container, false);
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
if (getActionBar() != null) {
getActionBar().hide();
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onAttach(@NonNull View view) {
super.onAttach(view);
checkIfWeAreSecure();
}
@RequiresApi(api = Build.VERSION_CODES.M)
@OnClick(R.id.unlockTextView)
void unlock() {
checkIfWeAreSecure();
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void showBiometricDialog() {
Context context = getActivity();
if (context != null) {
final BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
.setTitle(String.format(context.getString(R.string.nc_biometric_unlock),
context.getString(R.string.nc_app_name)))
.setNegativeButtonText(context.getString(R.string.nc_cancel))
.build();
Executor executor = Executors.newSingleThreadExecutor();
final BiometricPrompt biometricPrompt =
new BiometricPrompt((FragmentActivity) context, executor,
new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationSucceeded(
@NonNull BiometricPrompt.AuthenticationResult result) {
super.onAuthenticationSucceeded(result);
Log.d(TAG, "Fingerprint recognised successfully");
new Handler(Looper.getMainLooper()).post(
() -> getRouter().popCurrentController());
}
@Override
public void onAuthenticationFailed() {
super.onAuthenticationFailed();
Log.d(TAG, "Fingerprint not recognised");
}
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
super.onAuthenticationError(errorCode, errString);
showAuthenticationScreen();
}
}
);
BiometricPrompt.CryptoObject cryptoObject = SecurityUtils.getCryptoObject();
if (cryptoObject != null) {
biometricPrompt.authenticate(promptInfo, cryptoObject);
} else {
biometricPrompt.authenticate(promptInfo);
}
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private void checkIfWeAreSecure() {
if (getActivity() != null) {
KeyguardManager keyguardManager =
(KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE);
if (keyguardManager != null
&& keyguardManager.isKeyguardSecure()
&& appPreferences.getIsScreenLocked()) {
if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.getScreenLockTimeout())) {
showBiometricDialog();
} else {
getRouter().popCurrentController();
}
}
}
}
private void showAuthenticationScreen() {
if (getActivity() != null) {
KeyguardManager keyguardManager =
(KeyguardManager) getActivity().getSystemService(Context.KEYGUARD_SERVICE);
Intent intent = keyguardManager.createConfirmDeviceCredentialIntent(null, null);
if (intent != null) {
startActivityForResult(intent, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS);
}
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {
if (resultCode == Activity.RESULT_OK) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (SecurityUtils.checkIfWeAreAuthenticated(appPreferences.getScreenLockTimeout())) {
Log.d(TAG, "All went well, dismiss locked controller");
getRouter().popCurrentController();
}
}
} else {
Log.d(TAG, "Authorization failed");
}
}
}
}

View File

@ -0,0 +1,152 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.controllers
import android.app.Activity
import android.app.KeyguardManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricPrompt
import androidx.biometric.BiometricPrompt.PromptInfo
import androidx.fragment.app.FragmentActivity
import butterknife.OnClick
import com.nextcloud.talk.R
import com.nextcloud.talk.controllers.base.BaseController
import com.nextcloud.talk.utils.SecurityUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import org.koin.android.ext.android.inject
import java.util.concurrent.Executor
import java.util.concurrent.Executors
class LockedController : BaseController() {
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
return inflater.inflate(R.layout.controller_locked, container, false)
}
override fun onViewBound(view: View) {
super.onViewBound(view)
if (actionBar != null) {
actionBar!!.hide()
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
override fun onAttach(view: View) {
super.onAttach(view)
checkIfWeAreSecure()
}
@RequiresApi(api = Build.VERSION_CODES.M)
@OnClick(R.id.unlockTextView)
fun unlock() {
checkIfWeAreSecure()
}
@RequiresApi(api = Build.VERSION_CODES.M)
private fun showBiometricDialog() {
val context: Context? = activity
if (context != null) {
val promptInfo = PromptInfo.Builder()
.setTitle(String.format(context.getString(R.string.nc_biometric_unlock),
context.getString(R.string.nc_app_name)))
.setNegativeButtonText(context.getString(R.string.nc_cancel))
.build()
val executor: Executor = Executors.newSingleThreadExecutor()
val biometricPrompt = BiometricPrompt((context as FragmentActivity?)!!, executor,
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(
result: BiometricPrompt.AuthenticationResult) {
super.onAuthenticationSucceeded(result)
Log.d(TAG, "Fingerprint recognised successfully")
Handler(Looper.getMainLooper()).post { router.popCurrentController() }
}
override fun onAuthenticationFailed() {
super.onAuthenticationFailed()
Log.d(TAG, "Fingerprint not recognised")
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
super.onAuthenticationError(errorCode, errString)
showAuthenticationScreen()
}
}
)
val cryptoObject = SecurityUtils.getCryptoObject()
if (cryptoObject != null) {
biometricPrompt.authenticate(promptInfo, cryptoObject)
} else {
biometricPrompt.authenticate(promptInfo)
}
}
}
@RequiresApi(api = Build.VERSION_CODES.M)
private fun checkIfWeAreSecure() {
if (activity != null) {
val keyguardManager = activity!!.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
if (keyguardManager != null && keyguardManager.isKeyguardSecure
&& appPreferences!!.isScreenLocked) {
if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences!!.screenLockTimeout)) {
showBiometricDialog()
} else {
router.popCurrentController()
}
}
}
}
private fun showAuthenticationScreen() {
if (activity != null) {
val keyguardManager = activity!!.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
val intent = keyguardManager.createConfirmDeviceCredentialIntent(null, null)
intent?.let { startActivityForResult(it, REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) }
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS) {
if (resultCode == Activity.RESULT_OK) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (SecurityUtils.checkIfWeAreAuthenticated(appPreferences!!.screenLockTimeout)) {
Log.d(TAG, "All went well, dismiss locked controller")
router.popCurrentController()
}
}
} else {
Log.d(TAG, "Authorization failed")
}
}
}
companion object {
const val TAG = "LockedController"
private const val REQUEST_CODE_CONFIRM_DEVICE_CREDENTIALS = 112
}
}

View File

@ -21,7 +21,6 @@
package com.nextcloud.talk.controllers;
import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.media.MediaPlayer;
import android.media.RingtoneManager;
@ -34,29 +33,28 @@ import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import autodagger.AutoInjector;
import butterknife.BindView;
import com.bluelinelabs.logansquare.LoganSquare;
import com.nextcloud.talk.R;
import com.nextcloud.talk.adapters.items.NotificationSoundItem;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.controllers.base.BaseController;
import com.nextcloud.talk.models.RingtoneSettings;
import com.nextcloud.talk.utils.bundle.BundleKeys;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.SelectableAdapter;
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
@AutoInjector(NextcloudTalkApplication.class)
public class RingtoneSelectionController extends BaseController
implements FlexibleAdapter.OnItemClickListener {
@ -68,12 +66,6 @@ public class RingtoneSelectionController extends BaseController
@BindView(R.id.swipe_refresh_layout)
SwipeRefreshLayout swipeRefreshLayout;
@Inject
AppPreferences appPreferences;
@Inject
Context context;
private FlexibleAdapter adapter;
private RecyclerView.AdapterDataObserver adapterDataObserver;
private List<AbstractFlexibleItem> abstractFlexibleItemList = new ArrayList<>();
@ -97,9 +89,6 @@ public class RingtoneSelectionController extends BaseController
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
if (adapter == null) {
adapter = new FlexibleAdapter<>(abstractFlexibleItemList, getActivity(), false);

View File

@ -1,370 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.controllers;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.net.Uri;
import android.os.Bundle;
import android.security.KeyChain;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import autodagger.AutoInjector;
import butterknife.BindView;
import butterknife.OnClick;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.nextcloud.talk.R;
import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.controllers.base.BaseController;
import com.nextcloud.talk.utils.AccountUtils;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.bundle.BundleKeys;
import com.nextcloud.talk.utils.database.user.UserUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder;
import com.uber.autodispose.AutoDispose;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.security.cert.CertificateException;
import javax.inject.Inject;
import studio.carbonylgroup.textfieldboxes.ExtendedEditText;
import studio.carbonylgroup.textfieldboxes.TextFieldBoxes;
@AutoInjector(NextcloudTalkApplication.class)
public class ServerSelectionController extends BaseController {
public static final String TAG = "ServerSelectionController";
@BindView(R.id.extended_edit_text)
ExtendedEditText serverEntry;
@BindView(R.id.text_field_boxes)
TextFieldBoxes textFieldBoxes;
@BindView(R.id.progress_bar)
ProgressBar progressBar;
@BindView(R.id.helper_text_view)
TextView providersTextView;
@BindView(R.id.cert_text_view)
TextView certTextView;
@Inject
NcApi ncApi;
@Inject
UserUtils userUtils;
@Inject
AppPreferences appPreferences;
@Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_server_selection, container, false);
}
@SuppressLint("LongLogTag")
@OnClick(R.id.cert_text_view)
public void onCertClick() {
if (getActivity() != null) {
KeyChain.choosePrivateKeyAlias(getActivity(), alias -> {
if (alias != null) {
appPreferences.setTemporaryClientCertAlias(alias);
} else {
appPreferences.removeTemporaryClientCertAlias();
}
setCertTextView();
}, new String[] { "RSA", "EC" }, null, null, -1, null);
}
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
if (getActivity() != null) {
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
if (getActionBar() != null) {
getActionBar().hide();
}
textFieldBoxes.getEndIconImageButton()
.setBackgroundDrawable(getResources().getDrawable(R.drawable
.ic_arrow_forward_white_24px));
textFieldBoxes.getEndIconImageButton().setAlpha(0.5f);
textFieldBoxes.getEndIconImageButton().setEnabled(false);
textFieldBoxes.getEndIconImageButton().setVisibility(View.VISIBLE);
textFieldBoxes.getEndIconImageButton().setOnClickListener(view1 -> checkServerAndProceed());
if (TextUtils.isEmpty(getResources().getString(R.string.nc_providers_url))
&& (TextUtils.isEmpty(getResources
().getString(R.string.nc_import_account_type)))) {
providersTextView.setVisibility(View.INVISIBLE);
} else {
if ((TextUtils.isEmpty(getResources
().getString(R.string.nc_import_account_type)) ||
AccountUtils.INSTANCE.findAccounts(userUtils.getUsers()).size() == 0) &&
userUtils.getUsers().size() == 0) {
providersTextView.setText(R.string.nc_get_from_provider);
providersTextView.setOnClickListener(view12 -> {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(getResources()
.getString(R.string.nc_providers_url)));
startActivity(browserIntent);
});
} else if (AccountUtils.INSTANCE.findAccounts(userUtils.getUsers()).size() > 0) {
if (!TextUtils.isEmpty(AccountUtils.INSTANCE.getAppNameBasedOnPackage(getResources()
.getString(R.string.nc_import_accounts_from)))) {
if (AccountUtils.INSTANCE.findAccounts(userUtils.getUsers()).size() > 1) {
providersTextView.setText(String.format(getResources().getString(R.string
.nc_server_import_accounts),
AccountUtils.INSTANCE.getAppNameBasedOnPackage(getResources()
.getString(R.string.nc_import_accounts_from))));
} else {
providersTextView.setText(String.format(getResources().getString(R.string
.nc_server_import_account),
AccountUtils.INSTANCE.getAppNameBasedOnPackage(getResources()
.getString(R.string.nc_import_accounts_from))));
}
} else {
if (AccountUtils.INSTANCE.findAccounts(userUtils.getUsers()).size() > 1) {
providersTextView.setText(
getResources().getString(R.string.nc_server_import_accounts_plain));
} else {
providersTextView.setText(getResources().getString(R.string
.nc_server_import_account_plain));
}
}
providersTextView.setOnClickListener(view13 -> {
Bundle bundle = new Bundle();
bundle.putBoolean(BundleKeys.INSTANCE.getKEY_IS_ACCOUNT_IMPORT(), true);
getRouter().pushController(RouterTransaction.with(
new SwitchAccountController(bundle))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
});
} else {
providersTextView.setVisibility(View.INVISIBLE);
}
}
serverEntry.requestFocus();
serverEntry.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
if (!textFieldBoxes.isOnError() && !TextUtils.isEmpty(serverEntry.getText())) {
toggleProceedButton(true);
} else {
toggleProceedButton(false);
}
}
});
serverEntry.setOnEditorActionListener((textView, i, keyEvent) -> {
if (i == EditorInfo.IME_ACTION_DONE) {
checkServerAndProceed();
}
return false;
});
}
private void toggleProceedButton(boolean show) {
textFieldBoxes.getEndIconImageButton().setEnabled(show);
if (show) {
textFieldBoxes.getEndIconImageButton().setAlpha(1f);
} else {
textFieldBoxes.getEndIconImageButton().setAlpha(0.5f);
}
}
private void checkServerAndProceed() {
String url = serverEntry.getText().toString().trim();
serverEntry.setEnabled(false);
progressBar.setVisibility(View.VISIBLE);
if (providersTextView.getVisibility() != View.INVISIBLE) {
providersTextView.setVisibility(View.INVISIBLE);
certTextView.setVisibility(View.INVISIBLE);
}
if (url.endsWith("/")) {
url = url.substring(0, url.length() - 1);
}
String queryUrl = url + ApiUtils.getUrlPostfixForStatus();
if (url.startsWith("http://") || url.startsWith("https://")) {
checkServer(queryUrl, false);
} else {
checkServer("https://" + queryUrl, true);
}
}
private void checkServer(String queryUrl, boolean checkForcedHttps) {
ncApi.getServerStatus(queryUrl)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(status -> {
String productName = getResources().getString(R.string.nc_server_product_name);
String versionString = status.getVersion().substring(0, status.getVersion().indexOf("."));
int version = Integer.parseInt(versionString);
if (status.isInstalled() && !status.isMaintenance() &&
!status.isNeedsUpgrade() &&
version >= 13) {
getRouter().pushController(RouterTransaction.with(
new WebViewLoginController(queryUrl.replace("/status.php", ""),
false))
.pushChangeHandler(new HorizontalChangeHandler())
.popChangeHandler(new HorizontalChangeHandler()));
} else if (!status.isInstalled()) {
textFieldBoxes.setError(String.format(
getResources().getString(R.string.nc_server_not_installed), productName),
true);
toggleProceedButton(false);
} else if (status.isNeedsUpgrade()) {
textFieldBoxes.setError(String.format(getResources().
getString(R.string.nc_server_db_upgrade_needed),
productName), true);
toggleProceedButton(false);
} else if (status.isMaintenance()) {
textFieldBoxes.setError(String.format(getResources().
getString(R.string.nc_server_maintenance),
productName),
true);
toggleProceedButton(false);
} else if (!status.getVersion().startsWith("13.")) {
textFieldBoxes.setError(String.format(getResources().
getString(R.string.nc_server_version),
getResources().getString(R.string.nc_app_name)
, productName), true);
toggleProceedButton(false);
}
}, throwable -> {
if (checkForcedHttps) {
checkServer(queryUrl.replace("https://", "http://"), false);
} else {
if (throwable.getLocalizedMessage() != null) {
textFieldBoxes.setError(throwable.getLocalizedMessage(), true);
} else if (throwable.getCause() instanceof CertificateException) {
textFieldBoxes.setError(getResources().getString(R.string.nc_certificate_error),
false);
}
if (serverEntry != null) {
serverEntry.setEnabled(true);
}
progressBar.setVisibility(View.INVISIBLE);
if (providersTextView.getVisibility() != View.INVISIBLE) {
providersTextView.setVisibility(View.VISIBLE);
certTextView.setVisibility(View.VISIBLE);
}
toggleProceedButton(false);
}
}, () -> {
progressBar.setVisibility(View.INVISIBLE);
if (providersTextView.getVisibility() != View.INVISIBLE) {
providersTextView.setVisibility(View.VISIBLE);
certTextView.setVisibility(View.VISIBLE);
}
});
}
@Override
protected void onAttach(@NonNull View view) {
super.onAttach(view);
if (ApplicationWideMessageHolder.getInstance().getMessageType() != null) {
if (ApplicationWideMessageHolder.getInstance().getMessageType()
.equals(ApplicationWideMessageHolder.MessageType.ACCOUNT_SCHEDULED_FOR_DELETION)) {
textFieldBoxes.setError(
getResources().getString(R.string.nc_account_scheduled_for_deletion),
false);
ApplicationWideMessageHolder.getInstance().setMessageType(null);
} else if (ApplicationWideMessageHolder.getInstance().getMessageType()
.equals(ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK)) {
textFieldBoxes.setError(getResources().getString(R.string.nc_settings_no_talk_installed),
false);
} else if (ApplicationWideMessageHolder.getInstance().getMessageType()
.equals(ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT)) {
textFieldBoxes.setError(
getResources().getString(R.string.nc_server_failed_to_import_account),
false);
}
ApplicationWideMessageHolder.getInstance().setMessageType(null);
}
setCertTextView();
}
private void setCertTextView() {
if (getActivity() != null) {
getActivity().runOnUiThread(() -> {
if (!TextUtils.isEmpty(appPreferences.getTemporaryClientCertAlias())) {
certTextView.setText(R.string.nc_change_cert_auth);
} else {
certTextView.setText(R.string.nc_configure_cert_auth);
}
textFieldBoxes.setError("", true);
toggleProceedButton(true);
});
}
}
@Override
protected void onDestroyView(@NonNull View view) {
super.onDestroyView(view);
if (getActivity() != null) {
getActivity().setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
}
}
}

View File

@ -0,0 +1,327 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.controllers
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.ActivityInfo
import android.net.Uri
import android.os.Bundle
import android.security.KeyChain
import android.text.Editable
import android.text.TextUtils
import android.text.TextWatcher
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.ProgressBar
import android.widget.TextView
import autodagger.AutoInjector
import butterknife.BindView
import butterknife.OnClick
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
import com.nextcloud.talk.R
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.controllers.base.BaseController
import com.nextcloud.talk.models.json.generic.Status
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.utils.AccountUtils.findAccounts
import com.nextcloud.talk.utils.AccountUtils.getAppNameBasedOnPackage
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
import com.uber.autodispose.AutoDispose
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import org.koin.android.ext.android.inject
import studio.carbonylgroup.textfieldboxes.ExtendedEditText
import studio.carbonylgroup.textfieldboxes.TextFieldBoxes
import java.security.cert.CertificateException
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class ServerSelectionController : BaseController() {
@JvmField
@BindView(R.id.extended_edit_text)
var serverEntry: ExtendedEditText? = null
@JvmField
@BindView(R.id.text_field_boxes)
var textFieldBoxes: TextFieldBoxes? = null
@JvmField
@BindView(R.id.progress_bar)
var progressBar: ProgressBar? = null
@JvmField
@BindView(R.id.helper_text_view)
var providersTextView: TextView? = null
@JvmField
@BindView(R.id.cert_text_view)
var certTextView: TextView? = null
val usersRepository: UsersRepository by inject()
val ncApi: NcApi by inject()
override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View {
return inflater.inflate(R.layout.controller_server_selection, container, false)
}
@SuppressLint("LongLogTag")
@OnClick(R.id.cert_text_view)
fun onCertClick() {
if (activity != null) {
KeyChain.choosePrivateKeyAlias(activity!!, { alias: String? ->
if (alias != null) {
appPreferences.temporaryClientCertAlias = alias
} else {
appPreferences.removeTemporaryClientCertAlias()
}
setCertTextView()
}, arrayOf("RSA", "EC"), null, null, -1, null)
}
}
override fun onViewBound(view: View) {
super.onViewBound(view)
if (activity != null) {
activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}
if (actionBar != null) {
actionBar!!.hide()
}
textFieldBoxes!!.endIconImageButton
.setBackgroundDrawable(resources!!.getDrawable(R.drawable.ic_arrow_forward_white_24px))
textFieldBoxes!!.endIconImageButton.alpha = 0.5f
textFieldBoxes!!.endIconImageButton.isEnabled = false
textFieldBoxes!!.endIconImageButton.visibility = View.VISIBLE
textFieldBoxes!!.endIconImageButton.setOnClickListener { view1: View? -> checkServerAndProceed() }
if (TextUtils.isEmpty(resources!!.getString(R.string.nc_providers_url))
&& TextUtils.isEmpty(resources!!.getString(R.string.nc_import_account_type))) {
providersTextView!!.visibility = View.INVISIBLE
} else {
val users = usersRepository.getUsers()
val usersSize = users.count()
if ((TextUtils.isEmpty(resources!!.getString(R.string.nc_import_account_type)) ||
findAccounts(users).isEmpty()) &&
usersSize == 0) {
providersTextView!!.setText(R.string.nc_get_from_provider)
providersTextView!!.setOnClickListener { view12: View? ->
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(resources!!
.getString(R.string.nc_providers_url)))
startActivity(browserIntent)
}
} else if (findAccounts(users).isNotEmpty()) {
if (!TextUtils.isEmpty(getAppNameBasedOnPackage(resources!!
.getString(R.string.nc_import_accounts_from)))) {
if (findAccounts(users).size > 1) {
providersTextView!!.text = String.format(resources!!.getString(R.string.nc_server_import_accounts),
getAppNameBasedOnPackage(resources!!
.getString(R.string.nc_import_accounts_from)))
} else {
providersTextView!!.text = String.format(resources!!.getString(R.string.nc_server_import_account),
getAppNameBasedOnPackage(resources!!
.getString(R.string.nc_import_accounts_from)))
}
} else {
if (findAccounts(users).size > 1) {
providersTextView!!.text = resources!!.getString(R.string.nc_server_import_accounts_plain)
} else {
providersTextView!!.text = resources!!.getString(R.string.nc_server_import_account_plain)
}
}
providersTextView!!.setOnClickListener { view13: View? ->
val bundle = Bundle()
bundle.putBoolean(KEY_IS_ACCOUNT_IMPORT, true)
router.pushController(RouterTransaction.with(
SwitchAccountController(bundle))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler()))
}
} else {
providersTextView!!.visibility = View.INVISIBLE
}
}
serverEntry!!.requestFocus()
serverEntry!!.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun afterTextChanged(editable: Editable) {
if (!textFieldBoxes!!.isOnError && !TextUtils.isEmpty(serverEntry!!.text)) {
toggleProceedButton(true)
} else {
toggleProceedButton(false)
}
}
})
serverEntry!!.setOnEditorActionListener { textView: TextView?, i: Int, keyEvent: KeyEvent? ->
if (i == EditorInfo.IME_ACTION_DONE) {
checkServerAndProceed()
}
false
}
}
private fun toggleProceedButton(show: Boolean) {
textFieldBoxes!!.endIconImageButton.isEnabled = show
if (show) {
textFieldBoxes!!.endIconImageButton.alpha = 1f
} else {
textFieldBoxes!!.endIconImageButton.alpha = 0.5f
}
}
private fun checkServerAndProceed() {
var url = serverEntry!!.text.toString().trim { it <= ' ' }
serverEntry!!.isEnabled = false
progressBar!!.visibility = View.VISIBLE
if (providersTextView!!.visibility != View.INVISIBLE) {
providersTextView!!.visibility = View.INVISIBLE
certTextView!!.visibility = View.INVISIBLE
}
if (url.endsWith("/")) {
url = url.substring(0, url.length - 1)
}
val queryUrl = url + ApiUtils.getUrlPostfixForStatus()
if (url.startsWith("http://") || url.startsWith("https://")) {
checkServer(queryUrl, false)
} else {
checkServer("https://$queryUrl", true)
}
}
private fun checkServer(queryUrl: String, checkForcedHttps: Boolean) {
ncApi!!.getServerStatus(queryUrl)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.`as`(AutoDispose.autoDisposable(scopeProvider))
.subscribe({ status: Status ->
val productName = resources!!.getString(R.string.nc_server_product_name)
val versionString: String = status.version.substring(0, status.version.indexOf("."))
val version = versionString.toInt()
if (status.installed && !status.maintenance &&
!status.needsUpgrade && version >= 13) {
router.pushController(RouterTransaction.with(
WebViewLoginController(queryUrl.replace("/status.php", ""),
false))
.pushChangeHandler(HorizontalChangeHandler())
.popChangeHandler(HorizontalChangeHandler()))
} else if (!status.installed) {
textFieldBoxes!!.setError(String.format(
resources!!.getString(R.string.nc_server_not_installed), productName),
true)
toggleProceedButton(false)
} else if (status.needsUpgrade) {
textFieldBoxes!!.setError(String.format(resources!!.getString(R.string.nc_server_db_upgrade_needed),
productName), true)
toggleProceedButton(false)
} else if (status.maintenance) {
textFieldBoxes!!.setError(String.format(resources!!.getString(R.string.nc_server_maintenance),
productName),
true)
toggleProceedButton(false)
} else if (!status.version.startsWith("13.")) {
textFieldBoxes!!.setError(String.format(resources!!.getString(R.string.nc_server_version),
resources!!.getString(R.string.nc_app_name)
, productName), true)
toggleProceedButton(false)
}
}, { throwable: Throwable ->
if (checkForcedHttps) {
checkServer(queryUrl.replace("https://", "http://"), false)
} else {
if (throwable.localizedMessage != null) {
textFieldBoxes!!.setError(throwable.localizedMessage, true)
} else if (throwable.cause is CertificateException) {
textFieldBoxes!!.setError(resources!!.getString(R.string.nc_certificate_error),
false)
}
if (serverEntry != null) {
serverEntry!!.isEnabled = true
}
progressBar!!.visibility = View.INVISIBLE
if (providersTextView!!.visibility != View.INVISIBLE) {
providersTextView!!.visibility = View.VISIBLE
certTextView!!.visibility = View.VISIBLE
}
toggleProceedButton(false)
}
}) {
progressBar!!.visibility = View.INVISIBLE
if (providersTextView!!.visibility != View.INVISIBLE) {
providersTextView!!.visibility = View.VISIBLE
certTextView!!.visibility = View.VISIBLE
}
}
}
override fun onAttach(view: View) {
super.onAttach(view)
if (ApplicationWideMessageHolder.getInstance().messageType != null) {
when (ApplicationWideMessageHolder.getInstance().messageType
) {
ApplicationWideMessageHolder.MessageType.ACCOUNT_SCHEDULED_FOR_DELETION -> {
textFieldBoxes!!.setError(
resources!!.getString(R.string.nc_account_scheduled_for_deletion),
false)
ApplicationWideMessageHolder.getInstance().messageType = null
}
ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK -> {
textFieldBoxes!!.setError(resources!!.getString(R.string.nc_settings_no_talk_installed),
false)
}
ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT -> {
textFieldBoxes!!.setError(
resources!!.getString(R.string.nc_server_failed_to_import_account),
false)
}
}
ApplicationWideMessageHolder.getInstance().messageType = null
}
setCertTextView()
}
private fun setCertTextView() {
if (activity != null) {
activity!!.runOnUiThread {
if (!TextUtils.isEmpty(appPreferences.temporaryClientCertAlias)) {
certTextView!!.setText(R.string.nc_change_cert_auth)
} else {
certTextView!!.setText(R.string.nc_configure_cert_auth)
}
textFieldBoxes!!.setError("", true)
toggleProceedButton(true)
}
}
}
override fun onDestroyView(view: View) {
super.onDestroyView(view)
if (activity != null) {
activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
}
}
companion object {
const val TAG = "ServerSelectionController"
}
}

View File

@ -99,7 +99,6 @@ import java.util.Locale
import java.util.Objects
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class SettingsController : BaseController() {
@JvmField
@BindView(R.id.settings_screen)
@ -178,8 +177,7 @@ class SettingsController : BaseController() {
@BindView(R.id.message_text)
var messageText: TextView? = null
@JvmField
@Inject
var ncApi: NcApi? = null
val ncApi: NcApi by inject()
val usersRepository: UsersRepository by inject()
private var saveStateHandler: LovelySaveStateHandler? = null
private var currentUser: UserNgEntity? = null
@ -203,9 +201,6 @@ class SettingsController : BaseController() {
setHasOptionsMenu(true)
ViewCompat.setTransitionName(avatarImageView!!, "userAvatar.transitionTag")
NextcloudTalkApplication.sharedApplication!!
.componentApplication
.inject(this)
if (saveStateHandler == null) {
saveStateHandler = LovelySaveStateHandler()

View File

@ -29,12 +29,15 @@ import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import autodagger.AutoInjector;
import butterknife.BindView;
import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.nextcloud.talk.R;
@ -44,21 +47,24 @@ import com.nextcloud.talk.controllers.base.BaseController;
import com.nextcloud.talk.models.ImportAccount;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.participants.Participant;
import com.nextcloud.talk.newarch.local.models.UserNgEntity;
import com.nextcloud.talk.utils.AccountUtils;
import com.nextcloud.talk.utils.bundle.BundleKeys;
import com.nextcloud.talk.utils.database.user.UserUtils;
import com.uber.autodispose.AutoDispose;
import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import java.net.CookieManager;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
@AutoInjector(NextcloudTalkApplication.class)
public class SwitchAccountController extends BaseController {
@Inject
@ -164,9 +170,6 @@ public class SwitchAccountController extends BaseController {
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
swipeRefreshLayout.setEnabled(false);
if (getActionBar() != null) {
@ -176,7 +179,7 @@ public class SwitchAccountController extends BaseController {
if (adapter == null) {
adapter = new FlexibleAdapter<>(userItems, getActivity(), false);
UserEntity userEntity;
UserNgEntity userEntity;
Participant participant;
if (!isAccountImport) {
@ -208,8 +211,8 @@ public class SwitchAccountController extends BaseController {
importAccount = AccountUtils.INSTANCE.getInformationFromAccount(account);
participant = new Participant();
participant.setName(importAccount.getUsername());
participant.setUserId(importAccount.getUsername());
participant.name = importAccount.username;
participant.userId = importAccount.username;
userEntity = new UserEntity();
userEntity.setBaseUrl(importAccount.getBaseUrl());
userItems.add(new AdvancedUserItem(participant, userEntity, account));
@ -235,9 +238,9 @@ public class SwitchAccountController extends BaseController {
private void reauthorizeFromImport(Account account) {
ImportAccount importAccount = AccountUtils.INSTANCE.getInformationFromAccount(account);
Bundle bundle = new Bundle();
bundle.putString(BundleKeys.INSTANCE.getKEY_BASE_URL(), importAccount.getBaseUrl());
bundle.putString(BundleKeys.INSTANCE.getKEY_USERNAME(), importAccount.getUsername());
bundle.putString(BundleKeys.INSTANCE.getKEY_TOKEN(), importAccount.getToken());
bundle.putString(BundleKeys.INSTANCE.getKEY_BASE_URL(), importAccount.baseUrl);
bundle.putString(BundleKeys.INSTANCE.getKEY_USERNAME(), importAccount.username);
bundle.putString(BundleKeys.INSTANCE.getKEY_TOKEN(), importAccount.token);
bundle.putBoolean(BundleKeys.INSTANCE.getKEY_IS_ACCOUNT_IMPORT(), true);
getRouter().pushController(RouterTransaction.with(new AccountVerificationController(bundle))
.pushChangeHandler(new HorizontalChangeHandler())

View File

@ -35,7 +35,6 @@ import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import autodagger.AutoInjector;
import butterknife.BindView;
import butterknife.OnClick;
import com.bluelinelabs.conductor.RouterTransaction;
@ -57,11 +56,9 @@ import com.vanniktech.emoji.emoji.Emoji;
import com.vanniktech.emoji.listeners.OnEmojiClickListener;
import com.vanniktech.emoji.listeners.OnEmojiPopupDismissListener;
import com.vanniktech.emoji.listeners.OnEmojiPopupShownListener;
import javax.inject.Inject;
import org.greenrobot.eventbus.EventBus;
import org.parceler.Parcels;
@AutoInjector(NextcloudTalkApplication.class)
public class EntryMenuController extends BaseController {
@BindView(R.id.ok_button)
@ -76,12 +73,6 @@ public class EntryMenuController extends BaseController {
@BindView(R.id.smileyButton)
ImageView smileyButton;
@Inject
EventBus eventBus;
@Inject
UserUtils userUtils;
private int operationCode;
private Conversation conversation;
private Intent shareIntent;

View File

@ -73,7 +73,6 @@ import org.greenrobot.eventbus.EventBus;
import org.parceler.Parcels;
import retrofit2.HttpException;
@AutoInjector(NextcloudTalkApplication.class)
public class OperationsMenuController extends BaseController {
@BindView(R.id.progress_bar)
@ -160,10 +159,6 @@ public class OperationsMenuController extends BaseController {
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
processOperation();
}

View File

@ -1,36 +0,0 @@
/*
*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.dagger.modules;
import dagger.Module;
import dagger.Provides;
import javax.inject.Singleton;
import org.greenrobot.eventbus.EventBus;
@Module
public class BusModule {
@Provides
@Singleton
public EventBus provideEventBus() {
return EventBus.getDefault();
}
}

View File

@ -1,40 +0,0 @@
/*
*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.dagger.modules;
import android.content.Context;
import androidx.annotation.NonNull;
import dagger.Module;
import dagger.Provides;
@Module
public class ContextModule {
private final Context context;
public ContextModule(@NonNull final Context context) {
this.context = context;
}
@Provides
public Context provideContext() {
return context;
}
}

View File

@ -1,64 +0,0 @@
/*
*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.dagger.modules;
import android.content.Context;
import androidx.annotation.NonNull;
import com.nextcloud.talk.R;
import com.nextcloud.talk.models.database.Models;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import dagger.Module;
import dagger.Provides;
import io.requery.Persistable;
import io.requery.android.sqlcipher.SqlCipherDatabaseSource;
import io.requery.reactivex.ReactiveEntityStore;
import io.requery.reactivex.ReactiveSupport;
import io.requery.sql.Configuration;
import io.requery.sql.EntityDataStore;
import javax.inject.Singleton;
import net.orange_box.storebox.StoreBox;
@Module
public class DatabaseModule {
@Provides
@Singleton
public SqlCipherDatabaseSource provideSqlCipherDatabaseSource(@NonNull final Context context) {
return new SqlCipherDatabaseSource(context, Models.DEFAULT,
context.getResources().getString(R.string.nc_app_name).toLowerCase()
.replace(" ", "_").trim() + ".sqlite",
context.getString(R.string.nc_talk_database_encryption_key), 6);
}
@Provides
@Singleton
public ReactiveEntityStore<Persistable> provideDataStore(
@NonNull final SqlCipherDatabaseSource sqlCipherDatabaseSource) {
final Configuration configuration = sqlCipherDatabaseSource.getConfiguration();
return ReactiveSupport.toReactiveStore(new EntityDataStore<Persistable>(configuration));
}
@Provides
@Singleton
public AppPreferences providePreferences(@NonNull final Context poContext) {
return StoreBox.create(poContext, AppPreferences.class);
}
}

View File

@ -1,324 +0,0 @@
/*
*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.dagger.modules;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.github.aurae.retrofit2.LoganSquareConverterFactory;
import com.nextcloud.talk.BuildConfig;
import com.nextcloud.talk.R;
import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.LoggingUtils;
import com.nextcloud.talk.utils.database.user.UserUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.nextcloud.talk.utils.singletons.AvatarStatusCodeHolder;
import com.nextcloud.talk.utils.ssl.MagicKeyManager;
import com.nextcloud.talk.utils.ssl.MagicTrustManager;
import com.nextcloud.talk.utils.ssl.SSLSocketFactoryCompat;
import dagger.Module;
import dagger.Provides;
import io.reactivex.schedulers.Schedulers;
import java.io.IOException;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.concurrent.TimeUnit;
import javax.inject.Singleton;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.X509KeyManager;
import okhttp3.Authenticator;
import okhttp3.Cache;
import okhttp3.Credentials;
import okhttp3.Dispatcher;
import okhttp3.Interceptor;
import okhttp3.JavaNetCookieJar;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.Route;
import okhttp3.internal.tls.OkHostnameVerifier;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
@Module(includes = DatabaseModule.class)
public class RestModule {
private static final String TAG = "RestModule";
private final Context context;
public RestModule(Context context) {
this.context = context;
}
@Singleton
@Provides
NcApi provideNcApi(Retrofit retrofit) {
return retrofit.create(NcApi.class);
}
@Singleton
@Provides
Proxy provideProxy(AppPreferences appPreferences) {
if (!TextUtils.isEmpty(appPreferences.getProxyType()) && !"No proxy".equals(
appPreferences.getProxyType())
&& !TextUtils.isEmpty(appPreferences.getProxyHost())) {
GetProxyRunnable getProxyRunnable = new GetProxyRunnable(appPreferences);
Thread getProxyThread = new Thread(getProxyRunnable);
getProxyThread.start();
try {
getProxyThread.join();
return getProxyRunnable.getProxyValue();
} catch (InterruptedException e) {
Log.e(TAG, "Failed to join the thread while getting proxy: " + e.getLocalizedMessage());
return Proxy.NO_PROXY;
}
} else {
return Proxy.NO_PROXY;
}
}
@Singleton
@Provides
Retrofit provideRetrofit(OkHttpClient httpClient) {
Retrofit.Builder retrofitBuilder = new Retrofit.Builder()
.client(httpClient)
.baseUrl("https://nextcloud.com")
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(LoganSquareConverterFactory.create());
return retrofitBuilder.build();
}
@Singleton
@Provides
MagicTrustManager provideMagicTrustManager() {
return new MagicTrustManager();
}
@Singleton
@Provides
MagicKeyManager provideKeyManager(AppPreferences appPreferences, UserUtils userUtils) {
KeyStore keyStore = null;
try {
keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, null);
X509KeyManager origKm = (X509KeyManager) kmf.getKeyManagers()[0];
return new MagicKeyManager(origKm, userUtils, appPreferences);
} catch (KeyStoreException e) {
Log.e(TAG, "KeyStoreException " + e.getLocalizedMessage());
} catch (CertificateException e) {
Log.e(TAG, "CertificateException " + e.getLocalizedMessage());
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "NoSuchAlgorithmException " + e.getLocalizedMessage());
} catch (IOException e) {
Log.e(TAG, "IOException " + e.getLocalizedMessage());
} catch (UnrecoverableKeyException e) {
Log.e(TAG, "UnrecoverableKeyException " + e.getLocalizedMessage());
}
return null;
}
@Singleton
@Provides
SSLSocketFactoryCompat provideSslSocketFactoryCompat(MagicKeyManager keyManager, MagicTrustManager
magicTrustManager) {
return new SSLSocketFactoryCompat(keyManager, magicTrustManager);
}
@Singleton
@Provides
CookieManager provideCookieManager() {
CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
return cookieManager;
}
@Singleton
@Provides
Cache provideCache() {
return new Cache(NextcloudTalkApplication.Companion.getSharedApplication().getCacheDir(),
Long.MAX_VALUE);
}
@Singleton
@Provides
Dispatcher provideDispatcher() {
Dispatcher dispatcher = new Dispatcher();
dispatcher.setMaxRequestsPerHost(100);
dispatcher.setMaxRequests(100);
return dispatcher;
}
@Singleton
@Provides
OkHttpClient provideHttpClient(Proxy proxy, AppPreferences appPreferences,
MagicTrustManager magicTrustManager,
SSLSocketFactoryCompat sslSocketFactoryCompat, Cache cache,
CookieManager cookieManager, Dispatcher dispatcher) {
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
httpClient.retryOnConnectionFailure(true);
httpClient.connectTimeout(45, TimeUnit.SECONDS);
httpClient.readTimeout(45, TimeUnit.SECONDS);
httpClient.writeTimeout(45, TimeUnit.SECONDS);
httpClient.cookieJar(new JavaNetCookieJar(cookieManager));
httpClient.cache(cache);
// Trust own CA and all self-signed certs
httpClient.sslSocketFactory(sslSocketFactoryCompat, magicTrustManager);
httpClient.retryOnConnectionFailure(true);
httpClient.hostnameVerifier(magicTrustManager.getHostnameVerifier(OkHostnameVerifier.INSTANCE));
httpClient.dispatcher(dispatcher);
if (!Proxy.NO_PROXY.equals(proxy)) {
httpClient.proxy(proxy);
if (appPreferences.getProxyCredentials() &&
!TextUtils.isEmpty(appPreferences.getProxyUsername()) &&
!TextUtils.isEmpty(appPreferences.getProxyPassword())) {
httpClient.proxyAuthenticator(new MagicAuthenticator(Credentials.basic(
appPreferences.getProxyUsername(),
appPreferences.getProxyPassword()), "Proxy-Authorization"));
}
}
httpClient.addInterceptor(new HeadersInterceptor());
if (BuildConfig.DEBUG && !context.getResources().getBoolean(R.bool.nc_is_debug)) {
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
loggingInterceptor.redactHeader("Authorization");
loggingInterceptor.redactHeader("Proxy-Authorization");
httpClient.addInterceptor(loggingInterceptor);
} else if (context.getResources().getBoolean(R.bool.nc_is_debug)) {
HttpLoggingInterceptor.Logger fileLogger =
s -> LoggingUtils.INSTANCE.writeLogEntryToFile(context, s);
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(fileLogger);
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
loggingInterceptor.redactHeader("Authorization");
loggingInterceptor.redactHeader("Proxy-Authorization");
httpClient.addInterceptor(loggingInterceptor);
}
return httpClient.build();
}
public static class HeadersInterceptor implements Interceptor {
@NonNull
@Override
public Response intercept(@NonNull Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("User-Agent", ApiUtils.getUserAgent())
.header("Accept", "application/json")
.header("OCS-APIRequest", "true")
.method(original.method(), original.body())
.build();
Response response = chain.proceed(request);
if (request.url().encodedPath().contains("/avatar/")) {
AvatarStatusCodeHolder.getInstance().setStatusCode(response.code());
}
return response;
}
}
public static class MagicAuthenticator implements Authenticator {
private String credentials;
private String authenticatorType;
public MagicAuthenticator(@NonNull String credentials, @NonNull String authenticatorType) {
this.credentials = credentials;
this.authenticatorType = authenticatorType;
}
@Nullable
@Override
public Request authenticate(@Nullable Route route, @NonNull Response response) {
if (response.request().header(authenticatorType) != null) {
return null;
}
Response countedResponse = response;
int attemptsCount = 0;
while ((countedResponse = countedResponse.priorResponse()) != null) {
attemptsCount++;
if (attemptsCount == 3) {
return null;
}
}
return response.request().newBuilder()
.header(authenticatorType, credentials)
.build();
}
}
private class GetProxyRunnable implements Runnable {
private volatile Proxy proxy;
private AppPreferences appPreferences;
GetProxyRunnable(AppPreferences appPreferences) {
this.appPreferences = appPreferences;
}
@Override
public void run() {
if (Proxy.Type.SOCKS.equals(Proxy.Type.valueOf(appPreferences.getProxyType()))) {
proxy = new Proxy(Proxy.Type.valueOf(appPreferences.getProxyType()),
InetSocketAddress.createUnresolved(appPreferences.getProxyHost(), Integer.parseInt(
appPreferences.getProxyPort())));
} else {
proxy = new Proxy(Proxy.Type.valueOf(appPreferences.getProxyType()),
new InetSocketAddress(appPreferences.getProxyHost(),
Integer.parseInt(appPreferences.getProxyPort())));
}
}
Proxy getProxyValue() {
return proxy;
}
}
}

View File

@ -1,258 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.jobs;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import autodagger.AutoInjector;
import com.bluelinelabs.logansquare.LoganSquare;
import com.nextcloud.talk.R;
import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.generic.GenericOverall;
import com.nextcloud.talk.models.json.push.PushConfigurationState;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageUtils;
import com.nextcloud.talk.utils.database.user.UserUtils;
import com.nextcloud.talk.webrtc.WebSocketConnectionHelper;
import io.reactivex.CompletableObserver;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import java.io.IOException;
import java.net.CookieManager;
import java.util.HashMap;
import java.util.zip.CRC32;
import javax.inject.Inject;
import okhttp3.JavaNetCookieJar;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
@AutoInjector(NextcloudTalkApplication.class)
public class AccountRemovalWorker extends Worker {
public static final String TAG = "AccountRemovalWorker";
@Inject
UserUtils userUtils;
@Inject
ArbitraryStorageUtils arbitraryStorageUtils;
@Inject
Retrofit retrofit;
@Inject
OkHttpClient okHttpClient;
NcApi ncApi;
public AccountRemovalWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
PushConfigurationState pushConfigurationState;
String credentials;
for (Object userEntityObject : userUtils.getUsersScheduledForDeletion()) {
UserEntity userEntity = (UserEntity) userEntityObject;
try {
if (!TextUtils.isEmpty(userEntity.getPushConfigurationState())) {
pushConfigurationState = LoganSquare.parse(userEntity.getPushConfigurationState(),
PushConfigurationState.class);
PushConfigurationState finalPushConfigurationState = pushConfigurationState;
credentials = ApiUtils.getCredentials(userEntity.getUsername(), userEntity.getToken());
ncApi = retrofit.newBuilder().client(okHttpClient.newBuilder().cookieJar(new
JavaNetCookieJar(new CookieManager())).build()).build().create(NcApi.class);
String finalCredentials = credentials;
ncApi.unregisterDeviceForNotificationsWithNextcloud(credentials,
ApiUtils.getUrlNextcloudPush(userEntity
.getBaseUrl()))
.blockingSubscribe(new Observer<GenericOverall>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(GenericOverall genericOverall) {
if (genericOverall.getOcs().getMeta().getStatusCode() == 200
|| genericOverall.getOcs().getMeta().getStatusCode() == 202) {
HashMap<String, String> queryMap = new HashMap<>();
queryMap.put("deviceIdentifier",
finalPushConfigurationState.getDeviceIdentifier());
queryMap.put("userPublicKey", finalPushConfigurationState.getUserPublicKey());
queryMap.put("deviceIdentifierSignature",
finalPushConfigurationState.getDeviceIdentifierSignature());
ncApi.unregisterDeviceForNotificationsWithProxy
(ApiUtils.getUrlPushProxy(), queryMap)
.subscribe(new Observer<Void>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Void aVoid) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String groupName =
String.format(getApplicationContext().getResources()
.getString(R.string
.nc_notification_channel), userEntity.getUserId(),
userEntity.getBaseUrl());
CRC32 crc32 = new CRC32();
crc32.update(groupName.getBytes());
NotificationManager notificationManager =
(NotificationManager) getApplicationContext().getSystemService
(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) {
notificationManager.deleteNotificationChannelGroup(Long
.toString(crc32.getValue()));
}
}
WebSocketConnectionHelper.deleteExternalSignalingInstanceForUserEntity(
userEntity.getId());
arbitraryStorageUtils.deleteAllEntriesForAccountIdentifier(
userEntity.getId()).subscribe(new Observer() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(Object o) {
userUtils.deleteUser(userEntity.getId())
.subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
}
});
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
}
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
}
});
} else {
userUtils.deleteUser(userEntity.getId())
.subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
}
});
}
} catch (IOException e) {
Log.d(TAG, "Something went wrong while removing job at parsing PushConfigurationState");
userUtils.deleteUser(userEntity.getId())
.subscribe(new CompletableObserver() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
}
});
}
}
return Result.success();
}
}

View File

@ -0,0 +1,137 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.jobs
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.nextcloud.talk.R
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.local.dao.UsersDao
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageUtils
import com.nextcloud.talk.webrtc.WebSocketConnectionHelper.Companion.deleteExternalSignalingInstanceForUserEntity
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import okhttp3.JavaNetCookieJar
import okhttp3.OkHttpClient
import org.koin.core.KoinComponent
import org.koin.core.inject
import retrofit2.Retrofit
import java.net.CookieManager
import java.util.*
import java.util.zip.CRC32
class AccountRemovalWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams), KoinComponent {
val arbitraryStorageUtils: ArbitraryStorageUtils by inject()
val okHttpClient: OkHttpClient by inject()
val retrofit: Retrofit by inject()
private val usersDao: UsersDao by inject()
val usersRepository: UsersRepository by inject()
var ncApi: NcApi? = null
override suspend fun doWork(): Result {
val notificationManager = applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
for (userEntityObject in usersDao.getUsersScheduledForDeletion()) {
val userEntity: UserNgEntity = userEntityObject
val credentials = userEntity.getCredentials()
userEntity.pushConfiguration?.let {
ncApi = retrofit.newBuilder().client(okHttpClient.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build().create(NcApi::class.java)
ncApi!!.unregisterDeviceForNotificationsWithNextcloud(credentials,
ApiUtils.getUrlNextcloudPush(userEntity.baseUrl))
.blockingSubscribe(object : Observer<GenericOverall> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(genericOverall: GenericOverall) {
if (genericOverall.ocs.meta.statusCode == 200
|| genericOverall.ocs.meta.statusCode == 202) {
val queryMap = HashMap<String, String?>()
queryMap["deviceIdentifier"] = userEntity.pushConfiguration!!.deviceIdentifier
queryMap["userPublicKey"] = userEntity.pushConfiguration!!.userPublicKey
queryMap["deviceIdentifierSignature"] = userEntity.pushConfiguration!!.deviceIdentifierSignature
ncApi!!.unregisterDeviceForNotificationsWithProxy(ApiUtils.getUrlPushProxy(), queryMap)
.subscribe(object : Observer<Void> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(aVoid: Void) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val groupName = java.lang.String.format(applicationContext.resources
.getString(R.string.nc_notification_channel), userEntity.userId,
userEntity.baseUrl)
val crc32 = CRC32()
crc32.update(groupName.toByteArray())
notificationManager.deleteNotificationChannelGroup(java.lang.Long
.toString(crc32.value))
}
deleteExternalSignalingInstanceForUserEntity(
userEntity.id!!)
arbitraryStorageUtils!!.deleteAllEntriesForAccountIdentifier(
userEntity.id!!).subscribe(object : Observer<Any?> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(o: Any) {
GlobalScope.launch {
val job = async {
usersRepository.deleteUserWithId(userEntity.id!!)
}
job.await()
}
}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
}
}
override fun onError(e: Throwable) {}
override fun onComplete() {}
})
}?: run {
GlobalScope.launch {
val job = async {
usersRepository.deleteUserWithId(userEntity.id!!)
}
job.await()
}
}
}
return Result.success()
}
companion object {
const val TAG = "AccountRemovalWorker"
}
}

View File

@ -23,7 +23,6 @@ import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.Worker
import androidx.work.WorkerParameters
import autodagger.AutoInjector
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
@ -35,20 +34,14 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SELECTED_GROUPS
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_SELECTED_USERS
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN
import com.nextcloud.talk.utils.database.user.UserUtils
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus
import org.koin.core.KoinComponent
import org.koin.core.inject
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class AddParticipantsToConversation(context: Context,
workerParams: WorkerParameters) : CoroutineWorker(context, workerParams), KoinComponent {
@JvmField
@Inject
var ncApi: NcApi? = null
val ncApi: NcApi by inject()
val eventBus: EventBus by inject()
val usersRepository: UsersRepository by inject()
@ -63,24 +56,18 @@ class AddParticipantsToConversation(context: Context,
for (userId in selectedUserIds!!) {
retrofitBucket = ApiUtils.getRetrofitBucketForAddParticipant(user.baseUrl, conversationToken,
userId)
ncApi!!.addParticipant(credentials, retrofitBucket.url, retrofitBucket.queryMap)
ncApi.addParticipant(credentials, retrofitBucket.url, retrofitBucket.queryMap)
.subscribeOn(Schedulers.io())
.blockingSubscribe()
}
for (groupId in selectedGroupIds!!) {
retrofitBucket = ApiUtils.getRetrofitBucketForAddGroupParticipant(user.baseUrl, conversationToken,
groupId)
ncApi!!.addParticipant(credentials, retrofitBucket.url, retrofitBucket.queryMap)
ncApi.addParticipant(credentials, retrofitBucket.url, retrofitBucket.queryMap)
.subscribeOn(Schedulers.io())
.blockingSubscribe()
}
eventBus.post(EventStatus(user.id!!, EventStatus.EventType.PARTICIPANTS_UPDATE, true))
return Result.success()
}
init {
sharedApplication
?.componentApplication
?.inject(this)
}
}

View File

@ -55,7 +55,7 @@ class CapabilitiesWorker(context: Context, workerParams: WorkerParameters) : Cor
internalUserEntity.capabilities = capabilitiesOverall.ocs.data.capabilities
runBlocking {
val result = usersRepository.updateUser(internalUserEntity)
eventBus!!.post(EventStatus(internalUserEntity.id!!,
eventBus.post(EventStatus(internalUserEntity.id!!,
EventStatus.EventType.CAPABILITIES_FETCH, result > 0))
}
@ -64,7 +64,7 @@ class CapabilitiesWorker(context: Context, workerParams: WorkerParameters) : Cor
override suspend fun doWork(): Result {
val data = inputData
val internalUserId = data.getLong(KEY_INTERNAL_USER_ID, -1)
var userEntity: UserNgEntity?
val userEntity: UserNgEntity?
var userEntityObjectList: MutableList<UserNgEntity> = ArrayList()
if (internalUserId == -1L || usersRepository.getUserWithId(internalUserId) == null) {
userEntityObjectList = usersRepository.getUsers().toMutableList()
@ -74,20 +74,19 @@ class CapabilitiesWorker(context: Context, workerParams: WorkerParameters) : Cor
}
for (userEntityObject in userEntityObjectList) {
val internalUserEntity = userEntityObject
ncApi = retrofit.newBuilder().client(okHttpClient.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build().create(NcApi::class.java)
ncApi!!.getCapabilities(ApiUtils.getCredentials(internalUserEntity.username,
internalUserEntity.token),
ApiUtils.getUrlForCapabilities(internalUserEntity.baseUrl))
ncApi!!.getCapabilities(ApiUtils.getCredentials(userEntityObject.username,
userEntityObject.token),
ApiUtils.getUrlForCapabilities(userEntityObject.baseUrl))
.retry(3)
.blockingSubscribe(object : Observer<CapabilitiesOverall> {
override fun onSubscribe(d: Disposable) {}
override fun onNext(capabilitiesOverall: CapabilitiesOverall) {
updateUser(capabilitiesOverall, internalUserEntity)
updateUser(capabilitiesOverall, userEntityObject)
}
override fun onError(e: Throwable) {
eventBus.post(EventStatus(internalUserEntity.id!!,
eventBus.post(EventStatus(userEntityObject.id!!,
EventStatus.EventType.CAPABILITIES_FETCH, false))
}

View File

@ -1,117 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.jobs;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import autodagger.AutoInjector;
import com.nextcloud.talk.api.NcApi;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.events.EventStatus;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.generic.GenericOverall;
import com.nextcloud.talk.utils.ApiUtils;
import com.nextcloud.talk.utils.bundle.BundleKeys;
import com.nextcloud.talk.utils.database.user.UserUtils;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import java.net.CookieManager;
import javax.inject.Inject;
import okhttp3.JavaNetCookieJar;
import okhttp3.OkHttpClient;
import org.greenrobot.eventbus.EventBus;
import retrofit2.Retrofit;
@AutoInjector(NextcloudTalkApplication.class)
public class DeleteConversationWorker extends Worker {
@Inject
Retrofit retrofit;
@Inject
OkHttpClient okHttpClient;
@Inject
UserUtils userUtils;
@Inject
EventBus eventBus;
NcApi ncApi;
public DeleteConversationWorker(@NonNull Context context,
@NonNull WorkerParameters workerParams) {
super(context, workerParams);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
}
@NonNull
@Override
public Result doWork() {
Data data = getInputData();
long operationUserId = data.getLong(BundleKeys.INSTANCE.getKEY_INTERNAL_USER_ID(), -1);
String conversationToken = data.getString(BundleKeys.INSTANCE.getKEY_ROOM_TOKEN());
UserEntity operationUser = userUtils.getUserWithId(operationUserId);
if (operationUser != null) {
String credentials =
ApiUtils.getCredentials(operationUser.getUsername(), operationUser.getToken());
ncApi = retrofit.newBuilder().client(okHttpClient.newBuilder().cookieJar(new
JavaNetCookieJar(new CookieManager())).build()).build().create(NcApi.class);
EventStatus eventStatus = new EventStatus(operationUser.getId(),
EventStatus.EventType.CONVERSATION_UPDATE, true);
ncApi.deleteRoom(credentials, ApiUtils.getRoom(operationUser.getBaseUrl(), conversationToken))
.subscribeOn(Schedulers.io())
.blockingSubscribe(new Observer<GenericOverall>() {
Disposable disposable;
@Override
public void onSubscribe(Disposable d) {
disposable = d;
}
@Override
public void onNext(GenericOverall genericOverall) {
eventBus.postSticky(eventStatus);
}
@Override
public void onError(Throwable e) {
}
@Override
public void onComplete() {
disposable.dispose();
}
});
}
return Result.success();
}
}

View File

@ -0,0 +1,93 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.jobs
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import autodagger.AutoInjector
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.events.EventStatus
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.JavaNetCookieJar
import okhttp3.OkHttpClient
import org.greenrobot.eventbus.EventBus
import org.koin.core.KoinComponent
import org.koin.core.inject
import retrofit2.Retrofit
import java.net.CookieManager
@AutoInjector(NextcloudTalkApplication::class)
class DeleteConversationWorker(context: Context,
workerParams: WorkerParameters) : CoroutineWorker(context, workerParams), KoinComponent {
val retrofit: Retrofit by inject()
val okHttpClient: OkHttpClient by inject()
val eventBus: EventBus by inject()
val usersRepository: UsersRepository by inject()
var ncApi: NcApi? = null
override suspend fun doWork(): Result {
val data = inputData
val operationUserId = data.getLong(KEY_INTERNAL_USER_ID, -1)
val conversationToken = data.getString(KEY_ROOM_TOKEN)
val operationUser: UserNgEntity? = usersRepository.getUserWithId(operationUserId)
operationUser?.let {
val credentials = it.getCredentials()
ncApi = retrofit.newBuilder().client(okHttpClient.newBuilder().cookieJar(JavaNetCookieJar(CookieManager())).build()).build().create(NcApi::class.java)
val eventStatus = EventStatus(it.id!!,
EventStatus.EventType.CONVERSATION_UPDATE, true)
ncApi!!.deleteRoom(credentials, ApiUtils.getRoom(it.baseUrl, conversationToken))
.subscribeOn(Schedulers.io())
.blockingSubscribe(object : Observer<GenericOverall?> {
var disposable: Disposable? = null
override fun onSubscribe(d: Disposable) {
disposable = d
}
override fun onNext(genericOverall: GenericOverall) {
eventBus!!.postSticky(eventStatus)
}
override fun onError(e: Throwable) {}
override fun onComplete() {
disposable!!.dispose()
}
})
}
return Result.success()
}
init {
sharedApplication
?.componentApplication
?.inject(this)
}
}

View File

@ -50,7 +50,6 @@ import androidx.emoji.text.EmojiCompat
import androidx.work.ListenableWorker.Result
import androidx.work.Worker
import androidx.work.WorkerParameters
import autodagger.AutoInjector
import coil.Coil
import coil.target.Target
import coil.transform.CircleCropTransformation
@ -122,9 +121,7 @@ import java.util.function.Consumer
import java.util.zip.CRC32
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class NotificationWorker(
context: Context,
workerParams: WorkerParameters
@ -134,10 +131,8 @@ class NotificationWorker(
val retrofit: Retrofit by inject()
val okHttpClient: OkHttpClient by inject()
val usersRepository: UsersRepository by inject()
val arbitraryStorageUtils: ArbitraryStorageUtils by inject()
@JvmField
@Inject
var arbitraryStorageUtils: ArbitraryStorageUtils? = null
private var ncApi: NcApi? = null
private var decryptedPushMessage: DecryptedPushMessage? = null
private var context: Context? = null
@ -621,7 +616,6 @@ class NotificationWorker(
}
override fun doWork(): Result {
sharedApplication!!.componentApplication.inject(this)
context = applicationContext
val data = inputData
val subject =

View File

@ -22,10 +22,7 @@ package com.nextcloud.talk.jobs
import android.content.Context
import androidx.work.Worker
import androidx.work.WorkerParameters
import autodagger.AutoInjector
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
@ -38,17 +35,13 @@ import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.Collections
import javax.inject.Inject
import java.util.*
@AutoInjector(NextcloudTalkApplication::class)
class ShareOperationWorker(
context: Context,
workerParams: WorkerParameters
) : Worker(context, workerParams), KoinComponent {
@JvmField @Inject
var ncApi: NcApi? = null
val ncApi: NcApi by inject()
val usersRepository: UsersRepository by inject()
private val userId: Long
@ -79,9 +72,6 @@ class ShareOperationWorker(
}
init {
sharedApplication
?.componentApplication
?.inject(this)
val data = workerParams.inputData
userId = data.getLong(KEY_INTERNAL_USER_ID, 0)
roomToken = data.getString(KEY_ROOM_TOKEN)

View File

@ -46,17 +46,12 @@ import org.koin.core.inject
import java.util.*
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class SignalingSettingsWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams), KoinComponent {
@JvmField @Inject
var ncApi: NcApi? = null
val ncApi: NcApi by inject()
val eventBus: EventBus by inject()
val usersRepository: UsersRepository by inject()
override suspend fun doWork(): Result {
sharedApplication
?.componentApplication
?.inject(this)
val data = inputData
val internalUserId = data.getLong(KEY_INTERNAL_USER_ID, -1)
var userEntityList: MutableList<UserNgEntity?> = ArrayList()

View File

@ -30,11 +30,11 @@ import org.parceler.ParcelPropertyConverter;
@JsonObject
public class DataChannelMessage {
@JsonField(name = "type")
String type;
public String type;
@ParcelPropertyConverter(ObjectParcelConverter.class)
@JsonField(name = "payload")
Object payload;
public Object payload;
public DataChannelMessage(String type) {
this.type = type;

View File

@ -68,6 +68,15 @@ class ConversationsRepositoryImpl(val conversationsDao: ConversationsDao) :
)
}
override suspend fun getConversationForUserWithToken(userId: Long, token: String): Conversation? {
val conversationEntity = conversationsDao.getConversationForUserWithToken(userId, token)
if (conversationEntity != null) {
return conversationEntity.toConversation()
}
return null
}
override suspend fun clearConversationsForUser(userId: Long) {
conversationsDao
.clearConversationsForUser(userId)

View File

@ -20,13 +20,10 @@
package com.nextcloud.talk.newarch.data.source.remote
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.conversations.RoomsOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Url
import retrofit2.http.*
interface ApiService {
@ -64,4 +61,6 @@ interface ApiService {
@Url url: String
): GenericOverall
@GET
suspend fun getRoom(@Header("Authorization") authorization: String, @Url url: String): RoomOverall
}

View File

@ -30,10 +30,12 @@ import coil.decode.SvgDecoder
import com.github.aurae.retrofit2.LoganSquareConverterFactory
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.newarch.data.repository.online.NextcloudTalkRepositoryImpl
import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
import com.nextcloud.talk.newarch.data.source.remote.ApiService
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.domain.repository.online.NextcloudTalkRepository
import com.nextcloud.talk.newarch.utils.NetworkUtils
import com.nextcloud.talk.newarch.utils.NetworkUtils.GetProxyRunnable
@ -73,6 +75,7 @@ import javax.net.ssl.X509KeyManager
val NetworkModule = module {
single { createService(get()) }
single { createLegacyNcApi(get()) }
single { createRetrofit(get()) }
single { createProxy(get()) }
single { createTrustManager() }
@ -189,7 +192,7 @@ fun createSslSocketFactory(
fun createKeyManager(
appPreferences: AppPreferences,
userUtils: UserUtils
usersRepository: UsersRepository
): MagicKeyManager? {
val keyStore: KeyStore?
try {
@ -198,7 +201,7 @@ fun createKeyManager(
val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
kmf.init(keyStore, null)
val origKm = kmf.keyManagers[0] as X509KeyManager
return MagicKeyManager(origKm, userUtils, appPreferences)
return MagicKeyManager(origKm, usersRepository, appPreferences)
} catch (e: KeyStoreException) {
Log.e("NetworkModule", "KeyStoreException " + e.localizedMessage!!)
} catch (e: CertificateException) {
@ -255,6 +258,10 @@ fun createService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
fun createLegacyNcApi(retrofit: Retrofit): NcApi {
return retrofit.create(NcApi::class.java)
}
fun createImageLoader(
androidApplication: Application,
okHttpClient: OkHttpClient

View File

@ -33,6 +33,7 @@ import com.nextcloud.talk.newarch.local.dao.ConversationsDao
import com.nextcloud.talk.newarch.local.dao.MessagesDao
import com.nextcloud.talk.newarch.local.dao.UsersDao
import com.nextcloud.talk.newarch.local.db.TalkDatabase
import com.nextcloud.talk.utils.database.arbitrarystorage.ArbitraryStorageUtils
import com.nextcloud.talk.utils.database.user.UserUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import io.requery.Persistable
@ -49,10 +50,10 @@ val StorageModule = module {
single { createPreferences(androidContext()) }
single { createSqlCipherDatabaseSource(androidContext()) }
single { createDataStore(get()) }
single { createUserUtils(get()) }
single { createConversationsRepository(get()) }
single { createMessagesRepository(get()) }
single { createUsersRepository(get()) }
single { createArbitraryStorageUtils(get()) }
single { TalkDatabase.getInstance(androidApplication()) }
single { get<TalkDatabase>().conversationsDao() }
@ -89,6 +90,6 @@ fun createDataStore(sqlCipherDatabaseSource: SqlCipherDatabaseSource): ReactiveE
return ReactiveSupport.toReactiveStore(EntityDataStore(configuration))
}
fun createUserUtils(dataStore: ReactiveEntityStore<Persistable>): UserUtils {
return UserUtils(dataStore)
fun createArbitraryStorageUtils(dataStore: ReactiveEntityStore<Persistable>): ArbitraryStorageUtils {
return ArbitraryStorageUtils(dataStore)
}

View File

@ -25,6 +25,7 @@ import com.nextcloud.talk.models.json.conversations.Conversation
interface ConversationsRepository {
fun getConversationsForUser(userId: Long): LiveData<List<Conversation>>
suspend fun getConversationForUserWithToken(userId: Long, token: String): Conversation?
suspend fun clearConversationsForUser(userId: Long)
suspend fun saveConversationsForUser(
userId: Long,

View File

@ -22,6 +22,7 @@ package com.nextcloud.talk.newarch.domain.repository.online
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.models.json.generic.GenericOverall
import com.nextcloud.talk.newarch.local.models.UserNgEntity
@ -42,4 +43,9 @@ interface NextcloudTalkRepository {
userEntity: UserNgEntity,
conversation: Conversation
): GenericOverall
suspend fun getRoomForUser(
userEntity: UserNgEntity,
roomToken: String
): RoomOverall
}

View File

@ -0,0 +1,17 @@
package com.nextcloud.talk.newarch.domain.usecases
import com.nextcloud.talk.models.json.conversations.RoomOverall
import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
import com.nextcloud.talk.newarch.domain.repository.online.NextcloudTalkRepository
import com.nextcloud.talk.newarch.domain.usecases.base.UseCase
import org.koin.core.parameter.DefinitionParameters
class GetRoomUseCase constructor(
private val nextcloudTalkRepository: NextcloudTalkRepository,
apiErrorHandler: ApiErrorHandler?
) : UseCase<RoomOverall, Any?>(apiErrorHandler) {
override suspend fun run(params: Any?): RoomOverall {
val definitionParameters = params as DefinitionParameters
return nextcloudTalkRepository.getRoomForUser(definitionParameters[0], definitionParameters[1])
}
}

View File

@ -34,8 +34,8 @@ class LeaveConversationUseCase constructor(
override suspend fun run(params: Any?): GenericOverall {
val definitionParameters = params as DefinitionParameters
return nextcloudTalkRepository.leaveConversationForUser(
definitionParameters.get(0),
definitionParameters.get(1)
definitionParameters[0],
definitionParameters[1]
)
}
}

View File

@ -0,0 +1,436 @@
package com.nextcloud.talk.newarch.features.chat
import android.content.res.Resources
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.text.Editable
import android.text.InputFilter
import android.text.TextUtils
import android.text.TextWatcher
import android.view.*
import android.widget.AbsListView
import androidx.lifecycle.observe
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.api.load
import coil.target.Target
import coil.transform.CircleCropTransformation
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
import com.nextcloud.talk.R
import com.nextcloud.talk.adapters.messages.*
import com.nextcloud.talk.callbacks.MentionAutocompleteCallback
import com.nextcloud.talk.components.filebrowser.controllers.BrowserController
import com.nextcloud.talk.controllers.ChatController
import com.nextcloud.talk.models.json.chat.ChatMessage
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.mention.Mention
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.local.models.maxMessageLength
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
import com.nextcloud.talk.newarch.utils.Images
import com.nextcloud.talk.presenters.MentionAutocompletePresenter
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.DrawableUtils
import com.nextcloud.talk.utils.MagicCharPolicy
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_CONVERSATION_PASSWORD
import com.nextcloud.talk.utils.text.Spans
import com.otaliastudios.autocomplete.Autocomplete
import com.stfalcon.chatkit.commons.models.IMessage
import com.stfalcon.chatkit.messages.MessageHolders
import com.stfalcon.chatkit.messages.MessagesListAdapter
import com.stfalcon.chatkit.utils.DateFormatter
import kotlinx.android.synthetic.main.controller_chat.view.*
import kotlinx.android.synthetic.main.controller_conversations_rv.view.*
import kotlinx.android.synthetic.main.view_message_input.view.*
import org.koin.android.ext.android.inject
import org.parceler.Parcels
import java.util.*
import coil.ImageLoader as CoilImageLoader
import com.stfalcon.chatkit.commons.ImageLoader as ChatKitImageLoader
class ChatView : BaseView(), MessageHolders.ContentChecker<IMessage>, MessagesListAdapter.OnLoadMoreListener, MessagesListAdapter
.OnMessageLongClickListener<IMessage>, MessagesListAdapter.Formatter<Date> {
lateinit var viewModel: ChatViewModel
val factory: ChatViewModelFactory by inject()
val imageLoader: CoilImageLoader by inject()
var conversationInfoMenuItem: MenuItem? = null
var conversationVoiceCallMenuItem: MenuItem? = null
var conversationVideoMenuItem: MenuItem? = null
private var newMessagesCount = 0
lateinit var recyclerViewAdapter: MessagesListAdapter<ChatMessage>
lateinit var mentionAutocomplete: Autocomplete<*>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup
): View {
setHasOptionsMenu(true)
actionBar?.show()
viewModel = viewModelProvider(factory).get(ChatViewModel::class.java)
viewModel.init(args.getParcelable(BundleKeys.KEY_USER_ENTITY)!!, args.getString(BundleKeys.KEY_ROOM_TOKEN)!!, args.getString(KEY_CONVERSATION_PASSWORD))
viewModel.apply {
conversation.observe(this@ChatView) { value ->
setTitle()
setupAdapter()
if (Conversation.ConversationType.ONE_TO_ONE_CONVERSATION == value?.type) {
loadAvatar()
} else {
actionBar?.setIcon(null)
}
}
}
return super.onCreateView(inflater, container)
}
override fun onAttach(view: View) {
super.onAttach(view)
setupViews()
}
override fun onCreateOptionsMenu(
menu: Menu,
inflater: MenuInflater
) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.menu_conversation_plus_filter, menu)
conversationInfoMenuItem = menu.findItem(R.id.conversation_info)
conversationVoiceCallMenuItem = menu.findItem(R.id.conversation_voice_call)
conversationVideoMenuItem = menu.findItem(R.id.conversation_video_call)
}
private fun setupViews() {
view?.let {view ->
view.recyclerView.initRecyclerView(
LinearLayoutManager(view.context), recyclerViewAdapter, false
)
recyclerViewAdapter.setLoadMoreListener(this)
recyclerViewAdapter.setDateHeadersFormatter { format(it) }
recyclerViewAdapter.setOnMessageLongClickListener { onMessageLongClick(it) }
view.popupBubbleView.setRecyclerView(view.messagesListView)
view.popupBubbleView.setPopupBubbleListener { context ->
if (newMessagesCount != 0) {
val scrollPosition: Int
if (newMessagesCount - 1 < 0) {
scrollPosition = 0
} else {
scrollPosition = newMessagesCount - 1
}
view.messagesListView.postDelayed({
view.messagesListView.smoothScrollToPosition(scrollPosition)
}, 200)
}
}
view.messagesListView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(
recyclerView: RecyclerView,
newState: Int
) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
if (newMessagesCount != 0) {
val layoutManager: LinearLayoutManager = view.messagesListView.layoutManager as LinearLayoutManager
if (layoutManager.findFirstCompletelyVisibleItemPosition() <
newMessagesCount
) {
newMessagesCount = 0
view.popupBubbleView?.hide()
}
}
}
}
})
val filters = arrayOfNulls<InputFilter>(1)
val lengthFilter = viewModel.user.maxMessageLength()
filters[0] = InputFilter.LengthFilter(lengthFilter)
view.messageInput.filters = filters
view.messageInput.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(
s: CharSequence,
start: Int,
count: Int,
after: Int
) {
}
override fun onTextChanged(
s: CharSequence,
start: Int,
before: Int,
count: Int
) {
if (s.length >= lengthFilter) {
view.messageInput.error = String.format(
Objects.requireNonNull<Resources>
(resources).getString(R.string.nc_limit_hit), Integer.toString(lengthFilter)
)
} else {
view.messageInput.error = null
}
val editable = view.messageInput.editableText
if (editable != null) {
val mentionSpans = editable.getSpans(
0, view.messageInput.length(),
Spans.MentionChipSpan::class.java
)
var mentionSpan: Spans.MentionChipSpan
for (i in mentionSpans.indices) {
mentionSpan = mentionSpans[i]
if (start >= editable.getSpanStart(mentionSpan) && start < editable.getSpanEnd(
mentionSpan
)
) {
if (editable.subSequence(
editable.getSpanStart(mentionSpan),
editable.getSpanEnd(mentionSpan)
).toString().trim { it <= ' ' } != mentionSpan.label
) {
editable.removeSpan(mentionSpan)
}
}
}
}
}
override fun afterTextChanged(s: Editable) {
}
})
view.messageInputView?.setAttachmentsListener {
showBrowserScreen(
BrowserController
.BrowserType.DAV_BROWSER
)
}
view.messageInputView?.button?.setOnClickListener { submitMessage() }
view.messageInputView?.button?.contentDescription = resources?.getString(
R.string.nc_description_send_message_button
)
}
setupMentionAutocomplete()
}
private fun setupMentionAutocomplete() {
viewModel.conversation.value?.let { conversation ->
view?.let {view ->
val elevation = 6f
val backgroundDrawable = ColorDrawable(resources!!.getColor(R.color.bg_default))
val presenter = MentionAutocompletePresenter(context, conversation.token)
val callback = MentionAutocompleteCallback(
activity,
viewModel.user, view.messageInput
)
if (!::mentionAutocomplete.isInitialized) {
mentionAutocomplete = Autocomplete.on<Mention>(view.messageInput)
.with(elevation)
.with(backgroundDrawable)
.with(MagicCharPolicy('@'))
.with(presenter)
.with(callback)
.build()
}
}
}
}
private fun submitMessage() {
val editable = view?.messageInput?.editableText
editable?.let {
val mentionSpans = it.getSpans(
0, it.length,
Spans.MentionChipSpan::class.java
)
var mentionSpan: Spans.MentionChipSpan
for (i in mentionSpans.indices) {
mentionSpan = mentionSpans[i]
var mentionId = mentionSpan.id
if (mentionId.contains(" ") || mentionId.startsWith("guest/")) {
mentionId = "\"" + mentionId + "\""
}
it.replace(
it.getSpanStart(mentionSpan), it.getSpanEnd(mentionSpan), "@$mentionId"
)
}
view?.messageInput?.setText("")
viewModel.sendMessage(it)
}
}
private fun showBrowserScreen(browserType: BrowserController.BrowserType) {
viewModel.conversation.value?.let {
val bundle = Bundle()
bundle.putParcelable(
BundleKeys.KEY_BROWSER_TYPE, Parcels.wrap<BrowserController.BrowserType>(browserType)
)
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, Parcels.wrap<UserNgEntity>(viewModel.user))
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, it.token)
router.pushController(
RouterTransaction.with(BrowserController(bundle))
.pushChangeHandler(VerticalChangeHandler())
.popChangeHandler(VerticalChangeHandler())
)
}
}
private fun setupAdapter() {
val messageHolders = MessageHolders()
messageHolders.setIncomingTextConfig(
MagicIncomingTextMessageViewHolder::class.java, R.layout.item_custom_incoming_text_message
)
messageHolders.setOutcomingTextConfig(
MagicOutcomingTextMessageViewHolder::class.java,
R.layout.item_custom_outcoming_text_message
)
messageHolders.setIncomingImageConfig(
MagicPreviewMessageViewHolder::class.java, R.layout.item_custom_incoming_preview_message
)
messageHolders.setOutcomingImageConfig(
MagicPreviewMessageViewHolder::class.java, R.layout.item_custom_outcoming_preview_message
)
messageHolders.registerContentType(
ChatController.CONTENT_TYPE_SYSTEM_MESSAGE, MagicSystemMessageViewHolder::class.java,
R.layout.item_system_message, MagicSystemMessageViewHolder::class.java,
R.layout.item_system_message,
this
)
messageHolders.registerContentType(
ChatController.CONTENT_TYPE_UNREAD_NOTICE_MESSAGE,
MagicUnreadNoticeMessageViewHolder::class.java, R.layout.item_date_header,
MagicUnreadNoticeMessageViewHolder::class.java, R.layout.item_date_header, this
)
recyclerViewAdapter = MessagesListAdapter(
viewModel.user.userId, messageHolders, ChatKitImageLoader { imageView, url, payload ->
imageView.load(url) {
if (url!!.contains("/avatar/")) {
transformations(CircleCropTransformation())
} else {
if (payload is ImageLoaderPayload) {
payload.map?.let {
if (payload.map.containsKey("mimetype")) {
placeholder(
DrawableUtils.getDrawableResourceIdForMimeType(
payload.map.get("mimetype") as String?
)
)
}
}
}
}
if (url.startsWith(viewModel.user.baseUrl) && url.contains("index.php/core/preview?fileId=")) {
addHeader("Authorization", viewModel.user.getCredentials())
}
}
})
}
private fun loadAvatar() {
val avatarSize = DisplayUtils.convertDpToPixel(
conversationVoiceCallMenuItem?.icon!!
.intrinsicWidth.toFloat(), activity!!
)
.toInt()
avatarSize.let {
val target = object : Target {
override fun onSuccess(result: Drawable) {
super.onSuccess(result)
actionBar?.setIcon(result)
}
}
viewModel.conversation.value?.let {
val avatarRequest = Images().getRequestForUrl(
imageLoader, context, ApiUtils.getUrlForAvatarWithNameAndPixels(
viewModel.user.baseUrl,
it.name, avatarSize / 2
), null, target, this,
CircleCropTransformation()
);
imageLoader.load(avatarRequest)
}
}
}
override fun getLayoutId(): Int {
return R.layout.controller_chat
}
override fun getTitle(): String? {
return viewModel.conversation.value?.displayName
}
override fun hasContentFor(message: IMessage, type: Byte): Boolean {
when (type) {
ChatController.CONTENT_TYPE_SYSTEM_MESSAGE -> return !TextUtils.isEmpty(message.systemMessage)
ChatController.CONTENT_TYPE_UNREAD_NOTICE_MESSAGE -> return message.id == "-1"
}
return false
}
override fun format(date: Date): String {
return when {
DateFormatter.isToday(date) -> {
resources!!.getString(R.string.nc_date_header_today)
}
DateFormatter.isYesterday(date) -> {
resources!!.getString(R.string.nc_date_header_yesterday)
}
else -> {
DateFormatter.format(date, DateFormatter.Template.STRING_DAY_MONTH_YEAR)
}
}
}
override fun onLoadMore(page: Int, totalItemsCount: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun onMessageLongClick(message: IMessage?) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}

View File

@ -0,0 +1,30 @@
package com.nextcloud.talk.newarch.features.chat
import android.app.Application
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import kotlinx.coroutines.launch
class ChatViewModel constructor(application: Application, private val conversationsRepository: ConversationsRepository) : BaseViewModel<ChatView>(application) {
lateinit var user: UserNgEntity
val conversation: MutableLiveData<Conversation?> = MutableLiveData()
var conversationPassword: String? = null
fun init(user: UserNgEntity, conversationToken: String, conversationPassword: String?) {
viewModelScope.launch {
this@ChatViewModel.user = user
this@ChatViewModel.conversation.value = conversationsRepository.getConversationForUserWithToken(user.id!!, conversationToken)
this@ChatViewModel.conversationPassword = conversationPassword
}
}
fun sendMessage(message: CharSequence) {
}
}

View File

@ -0,0 +1,18 @@
package com.nextcloud.talk.newarch.features.chat
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository
class ChatViewModelFactory constructor(
private val application: Application,
private val conversationsRepository: ConversationsRepository
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return ChatViewModel(
application, conversationsRepository
) as T
}
}

View File

@ -0,0 +1,8 @@
package com.nextcloud.talk.newarch.features.chat
enum class ChatViewState {
INITIAL_LOAD,
LOBBY,
CHAT,
FAILED
}

View File

@ -26,7 +26,6 @@ import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
@ -59,10 +58,10 @@ import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
import com.nextcloud.talk.newarch.utils.Images
import com.nextcloud.talk.newarch.utils.ViewState.FAILED
import com.nextcloud.talk.newarch.utils.ViewState.LOADED
import com.nextcloud.talk.newarch.utils.ViewState.LOADED_EMPTY
import com.nextcloud.talk.newarch.utils.ViewState.LOADING
import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListViewState.FAILED
import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListViewState.LOADED
import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListViewState.LOADED_EMPTY
import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListViewState.LOADING
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.ConductorRemapping
import com.nextcloud.talk.utils.DisplayUtils

View File

@ -42,9 +42,8 @@ import com.nextcloud.talk.newarch.domain.usecases.LeaveConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.SetConversationFavoriteValueUseCase
import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.utils.ViewState.LOADING
import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListViewState.LOADING
import com.nextcloud.talk.utils.ShareUtils
import com.nextcloud.talk.utils.database.user.UserUtils
import kotlinx.coroutines.launch
import org.koin.core.parameter.parametersOf

View File

@ -18,9 +18,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.newarch.utils
package com.nextcloud.talk.newarch.features.conversationsList
enum class ViewState {
enum class ConversationsListViewState {
LOADING,
LOADED_EMPTY,
LOADED,

View File

@ -33,10 +33,10 @@ import com.nextcloud.talk.newarch.local.models.ConversationEntity
@Dao
abstract class ConversationsDao {
@Query("SELECT * FROM conversations WHERE user = :userId ORDER BY favorite DESC, last_activity DESC")
@Query("SELECT * FROM conversations WHERE user_id = :userId ORDER BY favorite DESC, last_activity DESC")
abstract fun getConversationsForUser(userId: Long): LiveData<List<ConversationEntity>>
@Query("DELETE FROM conversations WHERE user = :userId")
@Query("DELETE FROM conversations WHERE user_id = :userId")
abstract suspend fun clearConversationsForUser(userId: Long)
@Insert(onConflict = OnConflictStrategy.REPLACE)
@ -46,7 +46,7 @@ abstract class ConversationsDao {
abstract suspend fun saveConversationsWithInsert(vararg conversations: ConversationEntity): List<Long>
@Query(
"UPDATE conversations SET changing = :changing WHERE user = :userId AND conversation_id = :conversationId"
"UPDATE conversations SET changing = :changing WHERE user_id = :userId AND conversation_id = :conversationId"
)
abstract suspend fun updateChangingValueForConversation(
userId: Long,
@ -55,7 +55,7 @@ abstract class ConversationsDao {
)
@Query(
"UPDATE conversations SET favorite = :favorite, changing = 0 WHERE user = :userId AND conversation_id = :conversationId"
"UPDATE conversations SET favorite = :favorite, changing = 0 WHERE user_id = :userId AND conversation_id = :conversationId"
)
abstract suspend fun updateFavoriteValueForConversation(
userId: Long,
@ -63,7 +63,7 @@ abstract class ConversationsDao {
favorite: Boolean
)
@Query("DELETE FROM conversations WHERE user = :userId AND conversation_id = :conversationId")
@Query("DELETE FROM conversations WHERE user_id = :userId AND conversation_id = :conversationId")
abstract suspend fun deleteConversation(
userId: Long,
conversationId: String
@ -72,12 +72,15 @@ abstract class ConversationsDao {
@Delete
abstract suspend fun deleteConversations(vararg conversation: ConversationEntity)
@Query("DELETE FROM conversations WHERE user = :userId AND modified_at < :timestamp")
@Query("DELETE FROM conversations WHERE user_id = :userId AND modified_at < :timestamp")
abstract suspend fun deleteConversationsForUserWithTimestamp(
userId: Long,
timestamp: Long
)
@Query("SELECT * FROM conversations where id = :userId AND token = :token")
abstract suspend fun getConversationForUserWithToken(userId: Long, token: String): ConversationEntity?
@Transaction
open suspend fun updateConversationsForUser(
userId: Long,

View File

@ -37,11 +37,11 @@ import java.util.HashMap
@Entity(
tableName = "conversations",
indices = [Index(value = ["user", "conversation_id"], unique = true)],
indices = [Index(value = ["user_id", "token"], unique = true)],
foreignKeys = [ForeignKey(
entity = UserNgEntity::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("user"),
childColumns = arrayOf("user_id"),
onDelete = CASCADE,
onUpdate = CASCADE,
deferred = true
@ -49,7 +49,7 @@ import java.util.HashMap
)
data class ConversationEntity(
@PrimaryKey @ColumnInfo(name = "id") var id: String,
@ColumnInfo(name = "user") var user: Long? = null,
@ColumnInfo(name = "user_id") var user: Long? = null,
@ColumnInfo(name = "conversation_id") var conversationId: String? = null,
@ColumnInfo(name = "token") var token: String? = null,
@ColumnInfo(name = "name") var name: String? = null,
@ -117,7 +117,7 @@ fun ConversationEntity.toConversation(): Conversation {
}
fun Conversation.toConversationEntity(): ConversationEntity {
val conversationEntity = ConversationEntity(this.internalUserId.toString() + "@" + this.conversationId)
val conversationEntity = ConversationEntity(this.internalUserId.toString() + "@" + this.token)
conversationEntity.user = this.internalUserId
conversationEntity.conversationId = this.conversationId
conversationEntity.token = this.token

View File

@ -39,7 +39,7 @@ import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType
foreignKeys = [ForeignKey(
entity = ConversationEntity::class,
parentColumns = arrayOf("id"),
childColumns = arrayOf("conversation"),
childColumns = arrayOf("conversation_id"),
onDelete = CASCADE,
onUpdate = CASCADE,
deferred = true
@ -47,7 +47,7 @@ import com.nextcloud.talk.models.json.chat.ChatMessage.SystemMessageType
)
data class MessageEntity(
@PrimaryKey @ColumnInfo(name = "id") var id: String,
@ColumnInfo(name = "conversation") var conversation: String,
@ColumnInfo(name = "conversation_id") var conversation: String,
@ColumnInfo(name = "message_id") var messageId: Long = 0,
@ColumnInfo(name = "actor_id") var actorId: String? = null,
@ColumnInfo(name = "actor_type") var actorType: String? = null,

View File

@ -86,9 +86,5 @@ fun UserNgEntity.hasSpreedFeatureCapability(capabilityName: String): Boolean {
fun UserNgEntity.maxMessageLength(): Int {
val maxLength = capabilities?.spreedCapability?.config?.get("chat")
?.get("max-length")
if (maxLength != null) {
return maxLength.toInt()
} else {
return 1000
}
return maxLength?.toInt() ?: 1000
}

View File

@ -22,7 +22,6 @@ package com.nextcloud.talk.presenters
import android.content.Context
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import autodagger.AutoInjector
import com.nextcloud.talk.adapters.items.MentionAutocompleteItem
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
@ -42,13 +41,9 @@ import io.reactivex.schedulers.Schedulers
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.*
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
class MentionAutocompletePresenter : RecyclerViewPresenter<Mention?>, FlexibleAdapter.OnItemClickListener, KoinComponent {
@JvmField @Inject
var ncApi: NcApi? = null
val ncApi: NcApi by inject()
val usersRepository: UsersRepository by inject()
private var currentUser: UserNgEntity?
private var adapter: FlexibleAdapter<AbstractFlexibleItem<*>>? = null
@ -58,18 +53,12 @@ class MentionAutocompletePresenter : RecyclerViewPresenter<Mention?>, FlexibleAd
constructor(context: Context) : super(context) {
this.internalContext = context
sharedApplication
?.componentApplication
?.inject(this)
currentUser = usersRepository.getActiveUser()
}
constructor(context: Context, roomToken: String?) : super(context) {
this.roomToken = roomToken
this.internalContext = context
sharedApplication
?.componentApplication
?.inject(this)
currentUser = usersRepository.getActiveUser()
}

View File

@ -27,27 +27,21 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import autodagger.AutoInjector
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.utils.NotificationUtils
import com.nextcloud.talk.utils.database.user.UserUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import javax.inject.Inject
import org.koin.core.KoinComponent
import org.koin.core.inject
@AutoInjector(NextcloudTalkApplication::class)
class PackageReplacedReceiver : BroadcastReceiver() {
class PackageReplacedReceiver : BroadcastReceiver(), KoinComponent {
@Inject
lateinit var userUtils: UserUtils
@Inject
lateinit var appPreferences: AppPreferences
val userUtils: UserUtils by inject()
val appPreferences: AppPreferences by inject()
override fun onReceive(
context: Context,
intent: Intent?
) {
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
if (intent != null && intent.action != null &&
intent.action == "android.intent.action.MY_PACKAGE_REPLACED"

View File

@ -32,6 +32,7 @@ import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.models.ImportAccount
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import java.util.ArrayList
import java.util.Arrays
@ -39,14 +40,14 @@ object AccountUtils {
private val TAG = "AccountUtils"
fun findAccounts(userEntitiesList: List<UserEntity>): List<Account> {
fun findAccounts(userEntitiesList: List<UserNgEntity>): List<Account> {
val context = NextcloudTalkApplication.sharedApplication!!.applicationContext
val accMgr = AccountManager.get(context)
val accounts = accMgr.getAccountsByType(context.getString(R.string.nc_import_account_type))
val accountsAvailable = ArrayList<Account>()
var importAccount: ImportAccount
var internalUserEntity: UserEntity
var internalUserEntity: UserNgEntity
var accountFound: Boolean
for (account in accounts) {
accountFound = false

View File

@ -29,7 +29,6 @@ import android.content.Context
import android.os.Build
import android.service.notification.StatusBarNotification
import com.nextcloud.talk.R
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.utils.bundle.BundleKeys

View File

@ -24,11 +24,9 @@ import android.content.Context
import android.text.TextUtils
import android.util.Base64
import android.util.Log
import autodagger.AutoInjector
import com.bluelinelabs.logansquare.LoganSquare
import com.nextcloud.talk.R.string
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.events.EventStatus
import com.nextcloud.talk.events.EventStatus.EventType.PUSH_REGISTRATION
@ -44,42 +42,21 @@ import io.reactivex.Observer
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus
import java.io.File
import java.io.FileInputStream
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
import java.security.InvalidKeyException
import java.security.Key
import java.security.KeyFactory
import java.security.KeyPair
import java.security.KeyPairGenerator
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.PublicKey
import java.security.Signature
import java.security.SignatureException
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.io.*
import java.security.*
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import java.util.HashMap
import javax.inject.Inject
import java.util.*
import kotlin.experimental.and
@AutoInjector(NextcloudTalkApplication::class)
class PushUtils(val usersRepository: UsersRepository) {
@JvmField
@Inject
var userUtils: UserUtils? = null
@JvmField
@Inject
var appPreferences: AppPreferences? = null
@JvmField
@Inject
var eventBus: EventBus? = null
@JvmField
@Inject
var ncApi: NcApi? = null
class PushUtils(val usersRepository: UsersRepository): KoinComponent {
val userUtils: UserUtils by inject()
val appPreferences: AppPreferences by inject()
val eventBus: EventBus by inject()
val ncApi: NcApi by inject()
private val keysFile: File
private val publicKeyFile: File
private val privateKeyFile: File
@ -426,9 +403,6 @@ class PushUtils(val usersRepository: UsersRepository) {
}
init {
sharedApplication!!
.componentApplication
.inject(this)
keysFile = sharedApplication!!
.getDir("PushKeyStore", Context.MODE_PRIVATE)
publicKeyFile = File(

View File

@ -1,44 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.utils.database.arbitrarystorage;
import autodagger.AutoInjector;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.dagger.modules.DatabaseModule;
import dagger.Module;
import dagger.Provides;
import io.requery.Persistable;
import io.requery.reactivex.ReactiveEntityStore;
import javax.inject.Inject;
@Module(includes = DatabaseModule.class)
@AutoInjector(NextcloudTalkApplication.class)
public class ArbitraryStorageModule {
@Inject
public ArbitraryStorageModule() {
}
@Provides
public ArbitraryStorageUtils provideArbitraryStorageUtils(
ReactiveEntityStore<Persistable> dataStore) {
return new ArbitraryStorageUtils(dataStore);
}
}

View File

@ -32,7 +32,7 @@ import io.requery.reactivex.ReactiveScalar;
public class ArbitraryStorageUtils {
private ReactiveEntityStore<Persistable> dataStore;
ArbitraryStorageUtils(ReactiveEntityStore<Persistable> dataStore) {
public ArbitraryStorageUtils(ReactiveEntityStore<Persistable> dataStore) {
this.dataStore = dataStore;
}

View File

@ -1,43 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.utils.database.user;
import autodagger.AutoInjector;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.dagger.modules.DatabaseModule;
import dagger.Module;
import dagger.Provides;
import io.requery.Persistable;
import io.requery.reactivex.ReactiveEntityStore;
import javax.inject.Inject;
@Module(includes = DatabaseModule.class)
@AutoInjector(NextcloudTalkApplication.class)
public class UserModule {
@Inject
public UserModule() {
}
@Provides
public UserUtils provideUserUtils(ReactiveEntityStore<Persistable> dataStore) {
return new UserUtils(dataStore);
}
}

View File

@ -1,200 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This class is in part based on the code from the great people that wrote Signal
* https://github.com/signalapp/Signal-Android/raw/f9adb4e4554a44fd65b77320e34bf4bccf7924ce/src/org/thoughtcrime/securesms/webrtc/locks/LockManager.java
*/
package com.nextcloud.talk.utils.power;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.PowerManager;
import android.provider.Settings;
import autodagger.AutoInjector;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import javax.inject.Inject;
@AutoInjector(NextcloudTalkApplication.class)
public class PowerManagerUtils {
private static final String TAG = "PowerManagerUtils";
private final PowerManager.WakeLock fullLock;
private final PowerManager.WakeLock partialLock;
private final WifiManager.WifiLock wifiLock;
private final boolean wifiLockEnforced;
@Inject
Context context;
private ProximityLock proximityLock;
private boolean proximityDisabled = false;
private int orientation;
public PowerManagerUtils() {
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
fullLock =
pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP,
"nctalk:fullwakelock");
partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "nctalk:partialwakelock");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
proximityLock = new ProximityLock(pm);
}
// we suppress a possible leak because this is indeed application context
@SuppressLint("WifiManagerPotentialLeak") WifiManager wm =
(WifiManager) context.getSystemService(Context.WIFI_SERVICE);
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "nctalk:wifiwakelock");
fullLock.setReferenceCounted(false);
partialLock.setReferenceCounted(false);
wifiLock.setReferenceCounted(false);
wifiLockEnforced = isWifiPowerActiveModeEnabled(context);
orientation = context.getResources().getConfiguration().orientation;
}
public void setOrientation(int newOrientation) {
orientation = newOrientation;
updateInCallWakeLockState();
}
public void updatePhoneState(PhoneState state) {
switch (state) {
case IDLE:
setWakeLockState(WakeLockState.SLEEP);
break;
case PROCESSING:
setWakeLockState(WakeLockState.PARTIAL);
break;
case INTERACTIVE:
setWakeLockState(WakeLockState.FULL);
break;
case WITH_PROXIMITY_SENSOR_LOCK:
proximityDisabled = false;
updateInCallWakeLockState();
break;
case WITHOUT_PROXIMITY_SENSOR_LOCK:
proximityDisabled = true;
updateInCallWakeLockState();
break;
}
}
private void updateInCallWakeLockState() {
if (orientation != Configuration.ORIENTATION_LANDSCAPE
&& wifiLockEnforced
&& !proximityDisabled) {
setWakeLockState(WakeLockState.PROXIMITY);
} else {
setWakeLockState(WakeLockState.FULL);
}
}
private boolean isWifiPowerActiveModeEnabled(Context context) {
int wifi_pwr_active_mode =
Settings.Secure.getInt(context.getContentResolver(), "wifi_pwr_active_mode", -1);
return (wifi_pwr_active_mode != 0);
}
@SuppressLint("WakelockTimeout")
private synchronized void setWakeLockState(WakeLockState newState) {
switch (newState) {
case FULL:
if (!fullLock.isHeld()) {
fullLock.acquire();
}
if (!partialLock.isHeld()) {
partialLock.acquire();
}
if (!wifiLock.isHeld()) {
wifiLock.acquire();
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
proximityLock.release();
}
break;
case PARTIAL:
if (!partialLock.isHeld()) {
partialLock.acquire();
}
if (!wifiLock.isHeld()) {
wifiLock.acquire();
}
fullLock.release();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
proximityLock.release();
}
break;
case SLEEP:
fullLock.release();
partialLock.release();
wifiLock.release();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
proximityLock.release();
}
break;
case PROXIMITY:
if (!partialLock.isHeld()) {
partialLock.acquire();
}
if (!wifiLock.isHeld()) {
wifiLock.acquire();
}
fullLock.release(
);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
proximityLock.acquire();
}
break;
default:
// something went very very wrong
}
}
public enum PhoneState {
IDLE,
PROCESSING, //used when the phone is active but before the user should be alerted.
INTERACTIVE,
WITHOUT_PROXIMITY_SENSOR_LOCK,
WITH_PROXIMITY_SENSOR_LOCK
}
public enum WakeLockState {
FULL,
PARTIAL,
SLEEP,
PROXIMITY
}
}

View File

@ -0,0 +1,166 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* This class is in part based on the code from the great people that wrote Signal
* https://github.com/signalapp/Signal-Android/raw/f9adb4e4554a44fd65b77320e34bf4bccf7924ce/src/org/thoughtcrime/securesms/webrtc/locks/LockManager.java
*/
package com.nextcloud.talk.utils.power
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.WifiLock
import android.os.Build
import android.os.PowerManager
import android.os.PowerManager.WakeLock
import android.provider.Settings
import org.koin.core.KoinComponent
import org.koin.core.inject
class PowerManagerUtils: KoinComponent {
private val fullLock: WakeLock
private val partialLock: WakeLock
private val wifiLock: WifiLock
private val wifiLockEnforced: Boolean
val context: Context by inject()
private var proximityLock: ProximityLock? = null
private var proximityDisabled = false
private var orientation: Int
fun setOrientation(newOrientation: Int) {
orientation = newOrientation
updateInCallWakeLockState()
}
fun updatePhoneState(state: PhoneState?) {
when (state) {
PhoneState.IDLE -> setWakeLockState(WakeLockState.SLEEP)
PhoneState.PROCESSING -> setWakeLockState(WakeLockState.PARTIAL)
PhoneState.INTERACTIVE -> setWakeLockState(WakeLockState.FULL)
PhoneState.WITH_PROXIMITY_SENSOR_LOCK -> {
proximityDisabled = false
updateInCallWakeLockState()
}
PhoneState.WITHOUT_PROXIMITY_SENSOR_LOCK -> {
proximityDisabled = true
updateInCallWakeLockState()
}
}
}
private fun updateInCallWakeLockState() {
if (orientation != Configuration.ORIENTATION_LANDSCAPE && wifiLockEnforced
&& !proximityDisabled) {
setWakeLockState(WakeLockState.PROXIMITY)
} else {
setWakeLockState(WakeLockState.FULL)
}
}
private fun isWifiPowerActiveModeEnabled(context: Context?): Boolean {
val wifi_pwr_active_mode = Settings.Secure.getInt(context!!.contentResolver, "wifi_pwr_active_mode", -1)
return wifi_pwr_active_mode != 0
}
@SuppressLint("WakelockTimeout")
@Synchronized
private fun setWakeLockState(newState: WakeLockState) {
when (newState) {
WakeLockState.FULL -> {
if (!fullLock.isHeld) {
fullLock.acquire()
}
if (!partialLock.isHeld) {
partialLock.acquire()
}
if (!wifiLock.isHeld) {
wifiLock.acquire()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
proximityLock!!.release()
}
}
WakeLockState.PARTIAL -> {
if (!partialLock.isHeld) {
partialLock.acquire()
}
if (!wifiLock.isHeld) {
wifiLock.acquire()
}
fullLock.release()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
proximityLock!!.release()
}
}
WakeLockState.SLEEP -> {
fullLock.release()
partialLock.release()
wifiLock.release()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
proximityLock!!.release()
}
}
WakeLockState.PROXIMITY -> {
if (!partialLock.isHeld) {
partialLock.acquire()
}
if (!wifiLock.isHeld) {
wifiLock.acquire()
}
fullLock.release()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
proximityLock!!.acquire()
}
}
else -> {
}
}
}
enum class PhoneState {
IDLE, PROCESSING, //used when the phone is active but before the user should be alerted.
INTERACTIVE, WITHOUT_PROXIMITY_SENSOR_LOCK, WITH_PROXIMITY_SENSOR_LOCK
}
enum class WakeLockState {
FULL, PARTIAL, SLEEP, PROXIMITY
}
companion object {
private const val TAG = "PowerManagerUtils"
}
init {
val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
fullLock = pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP,
"nctalk:fullwakelock")
partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "nctalk:partialwakelock")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
proximityLock = ProximityLock(pm)
}
// we suppress a possible leak because this is indeed application context
@SuppressLint("WifiManagerPotentialLeak") val wm = context.getSystemService(Context.WIFI_SERVICE) as WifiManager
wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "nctalk:wifiwakelock")
fullLock.setReferenceCounted(false)
partialLock.setReferenceCounted(false)
wifiLock.setReferenceCounted(false)
wifiLockEnforced = isWifiPowerActiveModeEnabled(context)
orientation = context.resources.configuration.orientation
}
}

View File

@ -1,98 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.utils.preferences;
import android.app.Dialog;
import android.content.Context;
import android.os.Build;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import androidx.appcompat.app.AlertDialog;
import autodagger.AutoInjector;
import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.yarolegovich.mp.io.StandardUserInputModule;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
@AutoInjector(NextcloudTalkApplication.class)
public class MagicUserInputModule extends StandardUserInputModule {
@Inject
AppPreferences appPreferences;
private List<String> keysWithIntegerInput = new ArrayList<>();
public MagicUserInputModule(Context context) {
super(context);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
}
public MagicUserInputModule(Context context, List<String> keysWithIntegerInput) {
super(context);
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
this.keysWithIntegerInput = keysWithIntegerInput;
}
@Override
public void showEditTextInput(
String key,
CharSequence title,
CharSequence defaultValue,
final Listener<String> listener) {
final View view = LayoutInflater.from(context).inflate(R.layout.dialog_edittext, null);
final EditText inputField = view.findViewById(R.id.mp_text_input);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.getIsKeyboardIncognito()) {
inputField.setImeOptions(
inputField.getImeOptions() | EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING);
}
if (defaultValue != null) {
inputField.setText(defaultValue);
inputField.setSelection(defaultValue.length());
}
if (keysWithIntegerInput.contains(key)) {
inputField.setInputType(InputType.TYPE_CLASS_NUMBER);
}
final Dialog dialog = new AlertDialog.Builder(context)
.setTitle(title)
.setView(view)
.show();
view.findViewById(R.id.mp_btn_confirm).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
listener.onInput(inputField.getText().toString());
dialog.dismiss();
}
});
}
}

View File

@ -0,0 +1,73 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.utils.preferences
import android.app.Dialog
import android.content.Context
import android.os.Build
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.yarolegovich.mp.io.StandardUserInputModule
import com.yarolegovich.mp.io.UserInputModule
import org.koin.core.KoinComponent
import org.koin.core.inject
import java.util.*
class MagicUserInputModule : StandardUserInputModule, KoinComponent {
val appPreferences: AppPreferences by inject()
private var keysWithIntegerInput: List<String> = ArrayList()
constructor(context: Context?, keysWithIntegerInput: List<String>) : super(context) {
this.keysWithIntegerInput = keysWithIntegerInput
}
override fun showEditTextInput(
key: String,
title: CharSequence,
defaultValue: CharSequence,
listener: UserInputModule.Listener<String>) {
val view = LayoutInflater.from(context).inflate(R.layout.dialog_edittext, null)
val inputField = view.findViewById<EditText>(R.id.mp_text_input)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences!!.isKeyboardIncognito) {
inputField.imeOptions = inputField.imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
}
if (defaultValue != null) {
inputField.setText(defaultValue)
inputField.setSelection(defaultValue.length)
}
if (keysWithIntegerInput.contains(key)) {
inputField.inputType = InputType.TYPE_CLASS_NUMBER
}
val dialog: Dialog = AlertDialog.Builder(context)
.setTitle(title)
.setView(view)
.show()
view.findViewById<View>(R.id.mp_btn_confirm).setOnClickListener {
listener.onInput(inputField.text.toString())
dialog.dismiss()
}
}
}

View File

@ -22,7 +22,6 @@ package com.nextcloud.talk.utils.preferences.preferencestorage
import android.os.Bundle
import android.text.TextUtils
import autodagger.AutoInjector
import com.nextcloud.talk.api.NcApi
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
@ -37,21 +36,16 @@ import com.yarolegovich.mp.io.StorageModule
import io.reactivex.Observer
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
import org.koin.core.KoinComponent
import org.koin.core.inject
@AutoInjector(NextcloudTalkApplication::class)
class DatabaseStorageModule(
private val conversationUser: UserNgEntity,
private val conversationToken: String,
private val conversationInfoInterface: ConversationInfoInterface
) : StorageModule {
@JvmField
@Inject
var arbitraryStorageUtils: ArbitraryStorageUtils? =
null
@JvmField
@Inject
var ncApi: NcApi? = null
) : StorageModule, KoinComponent {
val arbitraryStorageUtils: ArbitraryStorageUtils by inject()
val ncApi: NcApi by inject()
private val accountIdentifier: Long
private var lobbyValue = false
private var favoriteConversationValue = false
@ -272,9 +266,6 @@ class DatabaseStorageModule(
override fun onRestoreInstanceState(savedState: Bundle) {}
init {
sharedApplication!!
.componentApplication
.inject(this)
accountIdentifier = conversationUser.id!!
}
}

View File

@ -1,202 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.utils.ssl;
import android.content.Context;
import android.security.KeyChain;
import android.security.KeyChainException;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.utils.database.user.UserUtils;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import java.net.Socket;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.net.ssl.X509KeyManager;
public class MagicKeyManager implements X509KeyManager {
private static final String TAG = "MagicKeyManager";
private final X509KeyManager keyManager;
private UserUtils userUtils;
private AppPreferences appPreferences;
private Context context;
public MagicKeyManager(X509KeyManager keyManager, UserUtils userUtils,
AppPreferences appPreferences) {
this.keyManager = keyManager;
this.userUtils = userUtils;
this.appPreferences = appPreferences;
context = NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext();
}
@Override
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
String alias;
if (!TextUtils.isEmpty(alias = userUtils.getCurrentUser().getClientCertificate()) ||
!TextUtils.isEmpty(alias = appPreferences.getTemporaryClientCertAlias())
&& new ArrayList<>(Arrays.asList(getClientAliases())).contains(alias)) {
return alias;
}
return null;
}
@Override
public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
return null;
}
private X509Certificate[] getCertificatesForAlias(@Nullable String alias) {
if (alias != null) {
GetCertificatesForAliasRunnable getCertificatesForAliasRunnable =
new GetCertificatesForAliasRunnable(alias);
Thread getCertificatesThread = new Thread(getCertificatesForAliasRunnable);
getCertificatesThread.start();
try {
getCertificatesThread.join();
return getCertificatesForAliasRunnable.getCertificates();
} catch (InterruptedException e) {
Log.e(TAG,
"Failed to join the thread while getting certificates: " + e.getLocalizedMessage());
}
}
return null;
}
private PrivateKey getPrivateKeyForAlias(@Nullable String alias) {
if (alias != null) {
GetPrivateKeyForAliasRunnable getPrivateKeyForAliasRunnable =
new GetPrivateKeyForAliasRunnable(alias);
Thread getPrivateKeyThread = new Thread(getPrivateKeyForAliasRunnable);
getPrivateKeyThread.start();
try {
getPrivateKeyThread.join();
return getPrivateKeyForAliasRunnable.getPrivateKey();
} catch (InterruptedException e) {
Log.e(TAG,
"Failed to join the thread while getting private key: " + e.getLocalizedMessage());
}
}
return null;
}
@Override
public X509Certificate[] getCertificateChain(String s) {
if (new ArrayList<>(Arrays.asList(getClientAliases())).contains(s)) {
return getCertificatesForAlias(s);
}
return null;
}
private String[] getClientAliases() {
Set<String> aliases = new HashSet<>();
String alias;
if (!TextUtils.isEmpty(alias = appPreferences.getTemporaryClientCertAlias())) {
aliases.add(alias);
}
List<UserEntity> userEntities = userUtils.getUsers();
for (int i = 0; i < userEntities.size(); i++) {
if (!TextUtils.isEmpty(alias = userEntities.get(i).getClientCertificate())) {
aliases.add(alias);
}
}
return aliases.toArray(new String[aliases.size()]);
}
@Override
public String[] getClientAliases(String s, Principal[] principals) {
return getClientAliases();
}
@Override
public String[] getServerAliases(String s, Principal[] principals) {
return null;
}
@Override
public PrivateKey getPrivateKey(String s) {
if (new ArrayList<>(Arrays.asList(getClientAliases())).contains(s)) {
return getPrivateKeyForAlias(s);
}
return null;
}
private class GetCertificatesForAliasRunnable implements Runnable {
private volatile X509Certificate[] certificates;
private String alias;
public GetCertificatesForAliasRunnable(String alias) {
this.alias = alias;
}
@Override
public void run() {
try {
certificates = KeyChain.getCertificateChain(context, alias);
} catch (KeyChainException | InterruptedException e) {
Log.e(TAG, e.getLocalizedMessage());
}
}
public X509Certificate[] getCertificates() {
return certificates;
}
}
private class GetPrivateKeyForAliasRunnable implements Runnable {
private volatile PrivateKey privateKey;
private String alias;
public GetPrivateKeyForAliasRunnable(String alias) {
this.alias = alias;
}
@Override
public void run() {
try {
privateKey = KeyChain.getPrivateKey(context, alias);
} catch (KeyChainException | InterruptedException e) {
Log.e(TAG, e.getLocalizedMessage());
}
}
public PrivateKey getPrivateKey() {
return privateKey;
}
}
}

View File

@ -0,0 +1,167 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.nextcloud.talk.utils.ssl
import android.content.Context
import android.security.KeyChain
import android.security.KeyChainException
import android.text.TextUtils
import android.util.Log
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.utils.preferences.AppPreferences
import java.net.Socket
import java.security.Principal
import java.security.PrivateKey
import java.security.cert.X509Certificate
import java.util.*
import javax.net.ssl.X509KeyManager
class MagicKeyManager(private val keyManager: X509KeyManager, private val usersRepository: UsersRepository,
private val appPreferences: AppPreferences) : X509KeyManager {
private val context: Context = sharedApplication!!.applicationContext
override fun chooseClientAlias(strings: Array<String>, principals: Array<Principal>, socket: Socket): String? {
var alias: String? = null
val user = usersRepository.getActiveUser()
user?.let {
it.clientCertificate?.let {
alias = it
}?: run {
appPreferences.temporaryClientCertAlias?.let {
alias = it
}
}
}
return alias
}
override fun chooseServerAlias(s: String, principals: Array<Principal>, socket: Socket): String? {
return null
}
private fun getCertificatesForAlias(alias: String?): Array<X509Certificate>? {
if (alias != null) {
val getCertificatesForAliasRunnable = GetCertificatesForAliasRunnable(alias)
val getCertificatesThread = Thread(getCertificatesForAliasRunnable)
getCertificatesThread.start()
try {
getCertificatesThread.join()
return getCertificatesForAliasRunnable.certificates
} catch (e: InterruptedException) {
Log.e(TAG,
"Failed to join the thread while getting certificates: " + e.localizedMessage)
}
}
return null
}
private fun getPrivateKeyForAlias(alias: String?): PrivateKey? {
if (alias != null) {
val getPrivateKeyForAliasRunnable = GetPrivateKeyForAliasRunnable(alias)
val getPrivateKeyThread = Thread(getPrivateKeyForAliasRunnable)
getPrivateKeyThread.start()
try {
getPrivateKeyThread.join()
return getPrivateKeyForAliasRunnable.privateKey
} catch (e: InterruptedException) {
Log.e(TAG,
"Failed to join the thread while getting private key: " + e.localizedMessage)
}
}
return null
}
override fun getCertificateChain(s: String): Array<X509Certificate>? {
return if (ArrayList(listOf(*clientAliases)).contains(s)) {
getCertificatesForAlias(s)!!
} else null
}
private val clientAliases: Array<String>
private get() {
val aliases: MutableSet<String> = HashSet()
var alias: String
if (!TextUtils.isEmpty(appPreferences.temporaryClientCertAlias.also { alias = it })) {
aliases.add(alias)
}
val userEntities: List<UserNgEntity> = usersRepository.getUsers()
for (i in userEntities.indices) {
userEntities[i].clientCertificate?.let {
aliases.add(it)
}
}
return aliases.toTypedArray()
}
override fun getClientAliases(s: String, principals: Array<Principal>): Array<String> {
return clientAliases
}
override fun getServerAliases(s: String, principals: Array<Principal>): Array<String>? {
return null
}
override fun getPrivateKey(s: String): PrivateKey? {
return if (ArrayList(listOf(*clientAliases)).contains(s)) {
getPrivateKeyForAlias(s)!!
} else null
}
private inner class GetCertificatesForAliasRunnable(private val alias: String) : Runnable {
@Volatile
lateinit var certificates: Array<X509Certificate>
override fun run() {
try {
certificates = KeyChain.getCertificateChain(context, alias) as Array<X509Certificate>
} catch (e: KeyChainException) {
Log.e(TAG, e.localizedMessage)
} catch (e: InterruptedException) {
Log.e(TAG, e.localizedMessage)
}
}
}
private inner class GetPrivateKeyForAliasRunnable(private val alias: String) : Runnable {
@Volatile
var privateKey: PrivateKey? = null
private set
override fun run() {
try {
privateKey = KeyChain.getPrivateKey(context, alias)
} catch (e: KeyChainException) {
Log.e(TAG, e.localizedMessage)
} catch (e: InterruptedException) {
Log.e(TAG, e.localizedMessage)
}
}
}
companion object {
private const val TAG = "MagicKeyManager"
}
}

View File

@ -1,509 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.nextcloud.talk.webrtc;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import autodagger.AutoInjector;
import com.bluelinelabs.logansquare.LoganSquare;
import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.events.MediaStreamEvent;
import com.nextcloud.talk.events.PeerConnectionEvent;
import com.nextcloud.talk.events.SessionDescriptionSendEvent;
import com.nextcloud.talk.events.WebSocketCommunicationEvent;
import com.nextcloud.talk.models.json.signaling.DataChannelMessage;
import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick;
import com.nextcloud.talk.models.json.signaling.NCIceCandidate;
import com.nextcloud.talk.utils.LoggingUtils;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import javax.inject.Inject;
import org.greenrobot.eventbus.EventBus;
import org.webrtc.DataChannel;
import org.webrtc.IceCandidate;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RtpReceiver;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
@AutoInjector(NextcloudTalkApplication.class)
public class MagicPeerConnectionWrapper {
private static String TAG = "MagicPeerConnectionWrapper";
@Inject
Context context;
private List<IceCandidate> iceCandidates = new ArrayList<>();
private PeerConnection peerConnection;
private String sessionId;
private String nick;
private MediaConstraints sdpConstraints;
private DataChannel magicDataChannel;
private MagicSdpObserver magicSdpObserver;
private MediaStream remoteMediaStream;
private boolean remoteVideoOn;
private boolean remoteAudioOn;
private boolean hasInitiated;
private MediaStream localMediaStream;
private boolean isMCUPublisher;
private boolean hasMCU;
private String videoStreamType;
private int connectionAttempts = 0;
private PeerConnection.IceConnectionState peerIceConnectionState;
public MagicPeerConnectionWrapper(PeerConnectionFactory peerConnectionFactory,
List<PeerConnection.IceServer> iceServerList,
MediaConstraints sdpConstraints,
String sessionId, String localSession, @Nullable MediaStream mediaStream,
boolean isMCUPublisher, boolean hasMCU, String videoStreamType) {
NextcloudTalkApplication.Companion.getSharedApplication()
.getComponentApplication()
.inject(this);
this.localMediaStream = mediaStream;
this.videoStreamType = videoStreamType;
this.hasMCU = hasMCU;
this.sessionId = sessionId;
this.sdpConstraints = sdpConstraints;
magicSdpObserver = new MagicSdpObserver();
hasInitiated = sessionId.compareTo(localSession) < 0;
this.isMCUPublisher = isMCUPublisher;
peerConnection = peerConnectionFactory.createPeerConnection(iceServerList, sdpConstraints,
new MagicPeerConnectionObserver());
if (peerConnection != null) {
if (localMediaStream != null) {
peerConnection.addStream(localMediaStream);
}
if (hasMCU || hasInitiated) {
DataChannel.Init init = new DataChannel.Init();
init.negotiated = false;
magicDataChannel = peerConnection.createDataChannel("status", init);
magicDataChannel.registerObserver(new MagicDataChannelObserver());
if (isMCUPublisher) {
peerConnection.createOffer(magicSdpObserver, sdpConstraints);
} else if (hasMCU) {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("sessionId", sessionId);
EventBus.getDefault()
.post(new WebSocketCommunicationEvent("peerReadyForRequestingOffer", hashMap));
} else if (hasInitiated) {
peerConnection.createOffer(magicSdpObserver, sdpConstraints);
}
}
}
}
public String getVideoStreamType() {
return videoStreamType;
}
public void removePeerConnection() {
if (magicDataChannel != null) {
magicDataChannel.dispose();
magicDataChannel = null;
}
if (peerConnection != null) {
if (localMediaStream != null) {
peerConnection.removeStream(localMediaStream);
}
peerConnection.close();
peerConnection = null;
}
}
public void drainIceCandidates() {
if (peerConnection != null) {
for (IceCandidate iceCandidate : iceCandidates) {
peerConnection.addIceCandidate(iceCandidate);
}
iceCandidates = new ArrayList<>();
}
}
public MagicSdpObserver getMagicSdpObserver() {
return magicSdpObserver;
}
public void addCandidate(IceCandidate iceCandidate) {
if (peerConnection != null && peerConnection.getRemoteDescription() != null) {
peerConnection.addIceCandidate(iceCandidate);
} else {
iceCandidates.add(iceCandidate);
}
}
public void sendNickChannelData(DataChannelMessageNick dataChannelMessage) {
ByteBuffer buffer;
if (magicDataChannel != null) {
try {
buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).getBytes());
magicDataChannel.send(new DataChannel.Buffer(buffer, false));
} catch (IOException e) {
Log.d(TAG,
"Failed to send channel data, attempting regular " + dataChannelMessage.toString());
}
}
}
public void sendChannelData(DataChannelMessage dataChannelMessage) {
ByteBuffer buffer;
if (magicDataChannel != null) {
try {
buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).getBytes());
magicDataChannel.send(new DataChannel.Buffer(buffer, false));
} catch (IOException e) {
Log.d(TAG,
"Failed to send channel data, attempting regular " + dataChannelMessage.toString());
}
}
}
public PeerConnection getPeerConnection() {
return peerConnection;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public String getNick() {
if (!TextUtils.isEmpty(nick)) {
return nick;
} else {
return NextcloudTalkApplication.Companion.getSharedApplication()
.getString(R.string.nc_nick_guest);
}
}
public void setNick(String nick) {
this.nick = nick;
}
private void sendInitialMediaStatus() {
if (localMediaStream != null) {
if (localMediaStream.videoTracks.size() == 1 && localMediaStream.videoTracks.get(0)
.enabled()) {
sendChannelData(new DataChannelMessage("videoOn"));
} else {
sendChannelData(new DataChannelMessage("videoOff"));
}
if (localMediaStream.audioTracks.size() == 1 && localMediaStream.audioTracks.get(0)
.enabled()) {
sendChannelData(new DataChannelMessage("audioOn"));
} else {
sendChannelData(new DataChannelMessage("audioOff"));
}
}
}
public boolean isMCUPublisher() {
return isMCUPublisher;
}
private void restartIce() {
if (connectionAttempts <= 5) {
if (!hasMCU || isMCUPublisher) {
MediaConstraints.KeyValuePair iceRestartConstraint =
new MediaConstraints.KeyValuePair("IceRestart", "true");
if (sdpConstraints.mandatory.contains(iceRestartConstraint)) {
sdpConstraints.mandatory.add(iceRestartConstraint);
}
peerConnection.createOffer(magicSdpObserver, sdpConstraints);
} else {
// we have an MCU and this is not the publisher
// Do something if we have an MCU
}
connectionAttempts++;
}
}
public PeerConnection.IceConnectionState getPeerIceConnectionState() {
return peerIceConnectionState;
}
private class MagicDataChannelObserver implements DataChannel.Observer {
@Override
public void onBufferedAmountChange(long l) {
}
@Override
public void onStateChange() {
if (magicDataChannel != null && magicDataChannel.state().equals(DataChannel.State.OPEN) &&
magicDataChannel.label().equals("status")) {
sendInitialMediaStatus();
}
}
@Override
public void onMessage(DataChannel.Buffer buffer) {
if (buffer.binary) {
Log.d(TAG, "Received binary msg over " + TAG + " " + sessionId);
return;
}
ByteBuffer data = buffer.data;
final byte[] bytes = new byte[data.capacity()];
data.get(bytes);
String strData = new String(bytes);
Log.d(TAG, "Got msg: " + strData + " over " + TAG + " " + sessionId);
LoggingUtils.INSTANCE.writeLogEntryToFile(context,
"Got msg: " + strData + " over " + peerConnection.hashCode() + " " + sessionId);
try {
DataChannelMessage dataChannelMessage =
LoganSquare.parse(strData, DataChannelMessage.class);
String internalNick;
if ("nickChanged".equals(dataChannelMessage.getType())) {
if (dataChannelMessage.getPayload() instanceof String) {
internalNick = (String) dataChannelMessage.getPayload();
if (!internalNick.equals(nick)) {
setNick(nick);
EventBus.getDefault()
.post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.NICK_CHANGE, sessionId, getNick(), null, videoStreamType));
}
} else {
if (dataChannelMessage.getPayload() != null) {
HashMap<String, String> payloadHashMap =
(HashMap<String, String>) dataChannelMessage.getPayload();
EventBus.getDefault()
.post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.NICK_CHANGE, payloadHashMap.get("userid"), payloadHashMap.get("name"), null,
videoStreamType));
}
}
} else if ("audioOn".equals(dataChannelMessage.getType())) {
remoteAudioOn = true;
EventBus.getDefault()
.post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.AUDIO_CHANGE, sessionId, null, remoteAudioOn, videoStreamType));
} else if ("audioOff".equals(dataChannelMessage.getType())) {
remoteAudioOn = false;
EventBus.getDefault()
.post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.AUDIO_CHANGE, sessionId, null, remoteAudioOn, videoStreamType));
} else if ("videoOn".equals(dataChannelMessage.getType())) {
remoteVideoOn = true;
EventBus.getDefault()
.post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.VIDEO_CHANGE, sessionId, null, remoteVideoOn, videoStreamType));
} else if ("videoOff".equals(dataChannelMessage.getType())) {
remoteVideoOn = false;
EventBus.getDefault()
.post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.VIDEO_CHANGE, sessionId, null, remoteVideoOn, videoStreamType));
}
} catch (IOException e) {
Log.d(TAG, "Failed to parse data channel message");
}
}
}
private class MagicPeerConnectionObserver implements PeerConnection.Observer {
private final String TAG = "MagicPeerConnectionObserver";
@Override
public void onSignalingChange(PeerConnection.SignalingState signalingState) {
}
@Override
public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
peerIceConnectionState = iceConnectionState;
LoggingUtils.INSTANCE.writeLogEntryToFile(context,
"iceConnectionChangeTo: "
+ iceConnectionState.name()
+ " over "
+ peerConnection.hashCode()
+ " "
+ sessionId);
Log.d("iceConnectionChangeTo: ",
iceConnectionState.name() + " over " + peerConnection.hashCode() + " " + sessionId);
if (iceConnectionState.equals(PeerConnection.IceConnectionState.CONNECTED)) {
connectionAttempts = 0;
/*EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.PEER_CONNECTED, sessionId, null, null));*/
if (!isMCUPublisher) {
EventBus.getDefault()
.post(new MediaStreamEvent(remoteMediaStream, sessionId, videoStreamType));
}
if (hasInitiated) {
sendInitialMediaStatus();
}
} else if (iceConnectionState.equals(PeerConnection.IceConnectionState.CLOSED)) {
EventBus.getDefault()
.post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.PEER_CLOSED, sessionId, null, null, videoStreamType));
connectionAttempts = 0;
} else if (iceConnectionState.equals(PeerConnection.IceConnectionState.FAILED)) {
/*if (MerlinTheWizard.isConnectedToInternet() && connectionAttempts < 5) {
restartIce();
}*/
if (isMCUPublisher) {
EventBus.getDefault()
.post(new PeerConnectionEvent(
PeerConnectionEvent.PeerConnectionEventType.PUBLISHER_FAILED, sessionId, null,
null, null));
}
}
}
@Override
public void onIceConnectionReceivingChange(boolean b) {
}
@Override
public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
}
@Override
public void onIceCandidate(IceCandidate iceCandidate) {
NCIceCandidate ncIceCandidate = new NCIceCandidate();
ncIceCandidate.setSdpMid(iceCandidate.sdpMid);
ncIceCandidate.setSdpMLineIndex(iceCandidate.sdpMLineIndex);
ncIceCandidate.setCandidate(iceCandidate.sdp);
EventBus.getDefault().post(new SessionDescriptionSendEvent(null, sessionId,
"candidate", ncIceCandidate, videoStreamType));
}
@Override
public void onIceCandidatesRemoved(IceCandidate[] iceCandidates) {
}
@Override
public void onAddStream(MediaStream mediaStream) {
remoteMediaStream = mediaStream;
}
@Override
public void onRemoveStream(MediaStream mediaStream) {
if (!isMCUPublisher) {
EventBus.getDefault().post(new MediaStreamEvent(null, sessionId, videoStreamType));
}
}
@Override
public void onDataChannel(DataChannel dataChannel) {
if (dataChannel.label().equals("status")) {
magicDataChannel = dataChannel;
magicDataChannel.registerObserver(new MagicDataChannelObserver());
}
}
@Override
public void onRenegotiationNeeded() {
}
@Override
public void onAddTrack(RtpReceiver rtpReceiver, MediaStream[] mediaStreams) {
}
}
private class MagicSdpObserver implements SdpObserver {
private final String TAG = "MagicSdpObserver";
@Override
public void onCreateFailure(String s) {
Log.d(TAG, s);
LoggingUtils.INSTANCE.writeLogEntryToFile(context,
"SDPObserver createFailure: "
+ s
+ " over "
+ peerConnection.hashCode()
+ " "
+ sessionId);
}
@Override
public void onSetFailure(String s) {
Log.d(TAG, s);
LoggingUtils.INSTANCE.writeLogEntryToFile(context,
"SDPObserver setFailure: " + s + " over " + peerConnection.hashCode() + " " + sessionId);
}
@Override
public void onCreateSuccess(SessionDescription sessionDescription) {
SessionDescription sessionDescriptionWithPreferredCodec;
String sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec
(sessionDescription.description,
"H264", false);
sessionDescriptionWithPreferredCodec = new SessionDescription(
sessionDescription.type,
sessionDescriptionStringWithPreferredCodec);
EventBus.getDefault()
.post(new SessionDescriptionSendEvent(sessionDescriptionWithPreferredCodec, sessionId,
sessionDescription.type.canonicalForm().toLowerCase(), null, videoStreamType));
if (peerConnection != null) {
peerConnection.setLocalDescription(magicSdpObserver, sessionDescriptionWithPreferredCodec);
}
}
@Override
public void onSetSuccess() {
if (peerConnection != null) {
if (peerConnection.getLocalDescription() == null) {
peerConnection.createAnswer(magicSdpObserver, sdpConstraints);
}
if (peerConnection.getRemoteDescription() != null) {
drainIceCandidates();
}
}
}
}
}

View File

@ -0,0 +1,395 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.nextcloud.talk.webrtc
import android.annotation.SuppressLint
import android.content.Context
import android.text.TextUtils
import android.util.Log
import com.bluelinelabs.logansquare.LoganSquare
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
import com.nextcloud.talk.events.MediaStreamEvent
import com.nextcloud.talk.events.PeerConnectionEvent
import com.nextcloud.talk.events.SessionDescriptionSendEvent
import com.nextcloud.talk.events.WebSocketCommunicationEvent
import com.nextcloud.talk.models.json.signaling.DataChannelMessage
import com.nextcloud.talk.models.json.signaling.DataChannelMessageNick
import com.nextcloud.talk.models.json.signaling.NCIceCandidate
import com.nextcloud.talk.utils.LoggingUtils.writeLogEntryToFile
import org.greenrobot.eventbus.EventBus
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.webrtc.*
import org.webrtc.PeerConnection.*
import java.io.IOException
import java.nio.ByteBuffer
import java.util.*
class MagicPeerConnectionWrapper(peerConnectionFactory: PeerConnectionFactory,
iceServerList: List<IceServer?>?,
sdpConstraints: MediaConstraints,
sessionId: String, localSession: String?, mediaStream: MediaStream?,
isMCUPublisher: Boolean, hasMCU: Boolean, videoStreamType: String): KoinComponent {
val context: Context by inject()
private var iceCandidates: MutableList<IceCandidate> = ArrayList()
var peerConnection: PeerConnection?
private set
var sessionId: String
private var nick: String? = null
private val sdpConstraints: MediaConstraints
private var magicDataChannel: DataChannel? = null
val magicSdpObserver: MagicSdpObserver
private var remoteMediaStream: MediaStream? = null
private var remoteVideoOn = false
private var remoteAudioOn = false
private val hasInitiated: Boolean
private val localMediaStream: MediaStream?
val isMCUPublisher: Boolean
private val hasMCU: Boolean
val videoStreamType: String
private var connectionAttempts = 0
var peerIceConnectionState: IceConnectionState? = null
private set
fun removePeerConnection() {
if (magicDataChannel != null) {
magicDataChannel!!.dispose()
magicDataChannel = null
}
if (peerConnection != null) {
if (localMediaStream != null) {
peerConnection!!.removeStream(localMediaStream)
}
peerConnection!!.close()
peerConnection = null
}
}
fun drainIceCandidates() {
if (peerConnection != null) {
for (iceCandidate in iceCandidates) {
peerConnection!!.addIceCandidate(iceCandidate)
}
iceCandidates = ArrayList()
}
}
fun addCandidate(iceCandidate: IceCandidate) {
if (peerConnection != null && peerConnection!!.remoteDescription != null) {
peerConnection!!.addIceCandidate(iceCandidate)
} else {
iceCandidates.add(iceCandidate)
}
}
@SuppressLint("LongLogTag")
fun sendNickChannelData(dataChannelMessage: DataChannelMessageNick) {
val buffer: ByteBuffer
if (magicDataChannel != null) {
try {
buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).toByteArray())
magicDataChannel!!.send(DataChannel.Buffer(buffer, false))
} catch (e: IOException) {
Log.d(TAG,
"Failed to send channel data, attempting regular $dataChannelMessage")
}
}
}
@SuppressLint("LongLogTag")
fun sendChannelData(dataChannelMessage: DataChannelMessage) {
val buffer: ByteBuffer
if (magicDataChannel != null) {
try {
buffer = ByteBuffer.wrap(LoganSquare.serialize(dataChannelMessage).toByteArray())
magicDataChannel!!.send(DataChannel.Buffer(buffer, false))
} catch (e: IOException) {
Log.d(TAG,
"Failed to send channel data, attempting regular $dataChannelMessage")
}
}
}
fun getNick(): String? {
return if (!TextUtils.isEmpty(nick)) {
nick
} else {
context.resources.getString(R.string.nc_nick_guest)
}
}
fun setNick(nick: String?) {
this.nick = nick
}
private fun sendInitialMediaStatus() {
if (localMediaStream != null) {
if (localMediaStream.videoTracks.size == 1 && localMediaStream.videoTracks[0]
.enabled()) {
sendChannelData(DataChannelMessage("videoOn"))
} else {
sendChannelData(DataChannelMessage("videoOff"))
}
if (localMediaStream.audioTracks.size == 1 && localMediaStream.audioTracks[0]
.enabled()) {
sendChannelData(DataChannelMessage("audioOn"))
} else {
sendChannelData(DataChannelMessage("audioOff"))
}
}
}
private fun restartIce() {
if (connectionAttempts <= 5) {
if (!hasMCU || isMCUPublisher) {
val iceRestartConstraint = MediaConstraints.KeyValuePair("IceRestart", "true")
if (sdpConstraints.mandatory.contains(iceRestartConstraint)) {
sdpConstraints.mandatory.add(iceRestartConstraint)
}
peerConnection!!.createOffer(magicSdpObserver, sdpConstraints)
} else { // we have an MCU and this is not the publisher
// Do something if we have an MCU
}
connectionAttempts++
}
}
private inner class MagicDataChannelObserver : DataChannel.Observer {
override fun onBufferedAmountChange(l: Long) {}
override fun onStateChange() {
if (magicDataChannel != null && magicDataChannel!!.state() == DataChannel.State.OPEN && magicDataChannel!!.label() == "status") {
sendInitialMediaStatus()
}
}
@SuppressLint("LongLogTag")
override fun onMessage(buffer: DataChannel.Buffer) {
if (buffer.binary) {
Log.d(TAG, "Received binary msg over $TAG $sessionId")
return
}
val data = buffer.data
val bytes = ByteArray(data.capacity())
data[bytes]
val strData = String(bytes)
Log.d(TAG, "Got msg: $strData over $TAG $sessionId")
writeLogEntryToFile(context,
"Got msg: " + strData + " over " + peerConnection.hashCode() + " " + sessionId)
try {
val dataChannelMessage = LoganSquare.parse(strData, DataChannelMessage::class.java)
val internalNick: String
if ("nickChanged" == dataChannelMessage.type) {
if (dataChannelMessage.payload is String) {
internalNick = dataChannelMessage.payload as String
if (internalNick != nick) {
setNick(nick)
EventBus.getDefault()
.post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.NICK_CHANGE, sessionId, getNick(), null, videoStreamType))
}
} else {
if (dataChannelMessage.payload != null) {
val payloadHashMap = dataChannelMessage.payload as HashMap<String, String>
EventBus.getDefault()
.post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.NICK_CHANGE, payloadHashMap["userid"], payloadHashMap["name"], null,
videoStreamType))
}
}
} else if ("audioOn" == dataChannelMessage.type) {
remoteAudioOn = true
EventBus.getDefault()
.post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.AUDIO_CHANGE, sessionId, null, remoteAudioOn, videoStreamType))
} else if ("audioOff" == dataChannelMessage.type) {
remoteAudioOn = false
EventBus.getDefault()
.post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.AUDIO_CHANGE, sessionId, null, remoteAudioOn, videoStreamType))
} else if ("videoOn" == dataChannelMessage.type) {
remoteVideoOn = true
EventBus.getDefault()
.post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.VIDEO_CHANGE, sessionId, null, remoteVideoOn, videoStreamType))
} else if ("videoOff" == dataChannelMessage.type) {
remoteVideoOn = false
EventBus.getDefault()
.post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.VIDEO_CHANGE, sessionId, null, remoteVideoOn, videoStreamType))
}
} catch (e: IOException) {
Log.d(TAG, "Failed to parse data channel message")
}
}
}
private inner class MagicPeerConnectionObserver : PeerConnection.Observer {
private val TAG = "MagicPeerConnectionObserver"
override fun onSignalingChange(signalingState: SignalingState) {}
override fun onIceConnectionChange(iceConnectionState: IceConnectionState) {
peerIceConnectionState = iceConnectionState
writeLogEntryToFile(context!!,
"iceConnectionChangeTo: "
+ iceConnectionState.name
+ " over "
+ peerConnection.hashCode()
+ " "
+ sessionId)
Log.d("iceConnectionChangeTo: ",
iceConnectionState.name + " over " + peerConnection.hashCode() + " " + sessionId)
if (iceConnectionState == IceConnectionState.CONNECTED) {
connectionAttempts = 0
/*EventBus.getDefault().post(new PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType
.PEER_CONNECTED, sessionId, null, null));*/if (!isMCUPublisher) {
EventBus.getDefault()
.post(MediaStreamEvent(remoteMediaStream, sessionId, videoStreamType))
}
if (hasInitiated) {
sendInitialMediaStatus()
}
} else if (iceConnectionState == IceConnectionState.CLOSED) {
EventBus.getDefault()
.post(PeerConnectionEvent(PeerConnectionEvent.PeerConnectionEventType.PEER_CLOSED, sessionId, null, null, videoStreamType))
connectionAttempts = 0
} else if (iceConnectionState == IceConnectionState.FAILED) { /*if (MerlinTheWizard.isConnectedToInternet() && connectionAttempts < 5) {
restartIce();
}*/
if (isMCUPublisher) {
EventBus.getDefault()
.post(PeerConnectionEvent(
PeerConnectionEvent.PeerConnectionEventType.PUBLISHER_FAILED, sessionId, null,
null, null))
}
}
}
override fun onIceConnectionReceivingChange(b: Boolean) {}
override fun onIceGatheringChange(iceGatheringState: IceGatheringState) {}
override fun onIceCandidate(iceCandidate: IceCandidate) {
val ncIceCandidate = NCIceCandidate()
ncIceCandidate.sdpMid = iceCandidate.sdpMid
ncIceCandidate.sdpMLineIndex = iceCandidate.sdpMLineIndex
ncIceCandidate.candidate = iceCandidate.sdp
EventBus.getDefault().post(SessionDescriptionSendEvent(null, sessionId,
"candidate", ncIceCandidate, videoStreamType))
}
override fun onIceCandidatesRemoved(iceCandidates: Array<IceCandidate>) {}
override fun onAddStream(mediaStream: MediaStream) {
remoteMediaStream = mediaStream
}
override fun onRemoveStream(mediaStream: MediaStream) {
if (!isMCUPublisher) {
EventBus.getDefault().post(MediaStreamEvent(null, sessionId, videoStreamType))
}
}
override fun onDataChannel(dataChannel: DataChannel) {
if (dataChannel.label() == "status") {
magicDataChannel = dataChannel
magicDataChannel!!.registerObserver(MagicDataChannelObserver())
}
}
override fun onRenegotiationNeeded() {}
override fun onAddTrack(rtpReceiver: RtpReceiver, mediaStreams: Array<MediaStream>) {}
}
inner class MagicSdpObserver : SdpObserver {
private val TAG = "MagicSdpObserver"
override fun onCreateFailure(s: String) {
Log.d(TAG, s)
writeLogEntryToFile(context!!,
"SDPObserver createFailure: "
+ s
+ " over "
+ peerConnection.hashCode()
+ " "
+ sessionId)
}
override fun onSetFailure(s: String) {
Log.d(TAG, s)
writeLogEntryToFile(context!!,
"SDPObserver setFailure: " + s + " over " + peerConnection.hashCode() + " " + sessionId)
}
override fun onCreateSuccess(sessionDescription: SessionDescription) {
val sessionDescriptionWithPreferredCodec: SessionDescription
val sessionDescriptionStringWithPreferredCodec = MagicWebRTCUtils.preferCodec(sessionDescription.description,
"H264", false)
sessionDescriptionWithPreferredCodec = SessionDescription(
sessionDescription.type,
sessionDescriptionStringWithPreferredCodec)
EventBus.getDefault()
.post(SessionDescriptionSendEvent(sessionDescriptionWithPreferredCodec, sessionId,
sessionDescription.type.canonicalForm().toLowerCase(), null, videoStreamType))
if (peerConnection != null) {
peerConnection!!.setLocalDescription(magicSdpObserver, sessionDescriptionWithPreferredCodec)
}
}
override fun onSetSuccess() {
if (peerConnection != null) {
if (peerConnection!!.localDescription == null) {
peerConnection!!.createAnswer(magicSdpObserver, sdpConstraints)
}
if (peerConnection!!.remoteDescription != null) {
drainIceCandidates()
}
}
}
}
companion object {
private const val TAG = "MagicPeerConnectionWrapper"
}
init {
localMediaStream = mediaStream
this.videoStreamType = videoStreamType
this.hasMCU = hasMCU
this.sessionId = sessionId
this.sdpConstraints = sdpConstraints
magicSdpObserver = MagicSdpObserver()
hasInitiated = sessionId.compareTo(localSession!!) < 0
this.isMCUPublisher = isMCUPublisher
peerConnection = peerConnectionFactory.createPeerConnection(iceServerList, sdpConstraints,
MagicPeerConnectionObserver())
if (peerConnection != null) {
if (localMediaStream != null) {
peerConnection!!.addStream(localMediaStream)
}
if (hasMCU || hasInitiated) {
val init = DataChannel.Init()
init.negotiated = false
magicDataChannel = peerConnection!!.createDataChannel("status", init)
magicDataChannel!!.registerObserver(MagicDataChannelObserver())
if (isMCUPublisher) {
peerConnection!!.createOffer(magicSdpObserver, sdpConstraints)
} else if (hasMCU) {
val hashMap = HashMap<String, String>()
hashMap["sessionId"] = sessionId
EventBus.getDefault()
.post(WebSocketCommunicationEvent("peerReadyForRequestingOffer", hashMap))
} else if (hasInitiated) {
peerConnection!!.createOffer(magicSdpObserver, sdpConstraints)
}
}
}
}
}

View File

@ -1,26 +1,6 @@
#
# Nextcloud Talk application
#
# @author Mario Danic
# Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
#Thu Aug 22 11:56:51 CEST 2019
#Wed Dec 11 12:48:55 CET 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip