mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-19 19:49:33 +01:00
Further migration steps
This commit is contained in:
parent
c3a9cca12d
commit
243fa2aad9
@ -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'
|
||||
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@
|
||||
package com.nextcloud.talk;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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?
|
||||
|
@ -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());
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
@ -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,208 +47,208 @@ 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
|
||||
UserUtils userUtils;
|
||||
@Inject
|
||||
UserUtils userUtils;
|
||||
|
||||
@BindView(R.id.recyclerView)
|
||||
RecyclerView recyclerView;
|
||||
@BindView(R.id.recyclerView)
|
||||
RecyclerView recyclerView;
|
||||
|
||||
@Inject
|
||||
CookieManager cookieManager;
|
||||
@Inject
|
||||
CookieManager cookieManager;
|
||||
|
||||
@BindView(R.id.swipe_refresh_layout)
|
||||
SwipeRefreshLayout swipeRefreshLayout;
|
||||
private FlexibleAdapter<AbstractFlexibleItem> adapter;
|
||||
private List<AbstractFlexibleItem> userItems = new ArrayList<>();
|
||||
@BindView(R.id.swipe_refresh_layout)
|
||||
SwipeRefreshLayout swipeRefreshLayout;
|
||||
private FlexibleAdapter<AbstractFlexibleItem> adapter;
|
||||
private List<AbstractFlexibleItem> userItems = new ArrayList<>();
|
||||
|
||||
private boolean isAccountImport = false;
|
||||
private boolean isAccountImport = false;
|
||||
|
||||
private FlexibleAdapter.OnItemClickListener onImportItemClickListener =
|
||||
new FlexibleAdapter.OnItemClickListener() {
|
||||
@Override
|
||||
public boolean onItemClick(View view, int position) {
|
||||
if (userItems.size() > position) {
|
||||
Account account = ((AdvancedUserItem) userItems.get(position)).getAccount();
|
||||
reauthorizeFromImport(account);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private FlexibleAdapter.OnItemClickListener onSwitchItemClickListener =
|
||||
new FlexibleAdapter.OnItemClickListener() {
|
||||
@Override
|
||||
public boolean onItemClick(View view, int position) {
|
||||
if (userItems.size() > position) {
|
||||
UserEntity userEntity = ((AdvancedUserItem) userItems.get(position)).getEntity();
|
||||
userUtils.createOrUpdateUser(null,
|
||||
null, null, null,
|
||||
null, true, null, userEntity.getId(), null, null, null)
|
||||
.as(AutoDispose.autoDisposable(getScopeProvider()))
|
||||
.subscribe(new Observer<UserEntity>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNext(UserEntity userEntity) {
|
||||
cookieManager.getCookieStore().removeAll();
|
||||
|
||||
userUtils.disableAllUsersWithoutId(userEntity.getId());
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> getRouter().popCurrentController());
|
||||
private FlexibleAdapter.OnItemClickListener onImportItemClickListener =
|
||||
new FlexibleAdapter.OnItemClickListener() {
|
||||
@Override
|
||||
public boolean onItemClick(View view, int position) {
|
||||
if (userItems.size() > position) {
|
||||
Account account = ((AdvancedUserItem) userItems.get(position)).getAccount();
|
||||
reauthorizeFromImport(account);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
private FlexibleAdapter.OnItemClickListener onSwitchItemClickListener =
|
||||
new FlexibleAdapter.OnItemClickListener() {
|
||||
@Override
|
||||
public boolean onItemClick(View view, int position) {
|
||||
if (userItems.size() > position) {
|
||||
UserEntity userEntity = ((AdvancedUserItem) userItems.get(position)).getEntity();
|
||||
userUtils.createOrUpdateUser(null,
|
||||
null, null, null,
|
||||
null, true, null, userEntity.getId(), null, null, null)
|
||||
.as(AutoDispose.autoDisposable(getScopeProvider()))
|
||||
.subscribe(new Observer<UserEntity>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public void onNext(UserEntity userEntity) {
|
||||
cookieManager.getCookieStore().removeAll();
|
||||
|
||||
return true;
|
||||
userUtils.disableAllUsersWithoutId(userEntity.getId());
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> getRouter().popCurrentController());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete() {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
public SwitchAccountController() {
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
public SwitchAccountController(Bundle args) {
|
||||
super();
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
if (args.containsKey(BundleKeys.INSTANCE.getKEY_IS_ACCOUNT_IMPORT())) {
|
||||
isAccountImport = true;
|
||||
}
|
||||
};
|
||||
|
||||
public SwitchAccountController() {
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
public SwitchAccountController(Bundle args) {
|
||||
super();
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
if (args.containsKey(BundleKeys.INSTANCE.getKEY_IS_ACCOUNT_IMPORT())) {
|
||||
isAccountImport = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
getRouter().popCurrentController();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_generic_rv, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
NextcloudTalkApplication.Companion.getSharedApplication()
|
||||
.getComponentApplication()
|
||||
.inject(this);
|
||||
swipeRefreshLayout.setEnabled(false);
|
||||
|
||||
if (getActionBar() != null) {
|
||||
getActionBar().show();
|
||||
}
|
||||
|
||||
if (adapter == null) {
|
||||
adapter = new FlexibleAdapter<>(userItems, getActivity(), false);
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
getRouter().popCurrentController();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
UserEntity userEntity;
|
||||
Participant participant;
|
||||
@Override
|
||||
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
|
||||
return inflater.inflate(R.layout.controller_generic_rv, container, false);
|
||||
}
|
||||
|
||||
if (!isAccountImport) {
|
||||
for (Object userEntityObject : userUtils.getUsers()) {
|
||||
userEntity = (UserEntity) userEntityObject;
|
||||
if (!userEntity.getCurrent()) {
|
||||
participant = new Participant();
|
||||
participant.setName(userEntity.getDisplayName());
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
swipeRefreshLayout.setEnabled(false);
|
||||
|
||||
String userId;
|
||||
if (getActionBar() != null) {
|
||||
getActionBar().show();
|
||||
}
|
||||
|
||||
if (userEntity.getUserId() != null) {
|
||||
userId = userEntity.getUserId();
|
||||
if (adapter == null) {
|
||||
adapter = new FlexibleAdapter<>(userItems, getActivity(), false);
|
||||
|
||||
UserNgEntity userEntity;
|
||||
Participant participant;
|
||||
|
||||
if (!isAccountImport) {
|
||||
for (Object userEntityObject : userUtils.getUsers()) {
|
||||
userEntity = (UserEntity) userEntityObject;
|
||||
if (!userEntity.getCurrent()) {
|
||||
participant = new Participant();
|
||||
participant.setName(userEntity.getDisplayName());
|
||||
|
||||
String userId;
|
||||
|
||||
if (userEntity.getUserId() != null) {
|
||||
userId = userEntity.getUserId();
|
||||
} else {
|
||||
userId = userEntity.getUsername();
|
||||
}
|
||||
participant.setUserId(userId);
|
||||
userItems.add(new AdvancedUserItem(participant, userEntity, null));
|
||||
}
|
||||
}
|
||||
|
||||
adapter.addListener(onSwitchItemClickListener);
|
||||
adapter.updateDataSet(userItems, false);
|
||||
} else {
|
||||
userId = userEntity.getUsername();
|
||||
Account account;
|
||||
ImportAccount importAccount;
|
||||
for (Object accountObject : AccountUtils.INSTANCE.findAccounts(userUtils.getUsers())) {
|
||||
account = (Account) accountObject;
|
||||
importAccount = AccountUtils.INSTANCE.getInformationFromAccount(account);
|
||||
|
||||
participant = new Participant();
|
||||
participant.name = importAccount.username;
|
||||
participant.userId = importAccount.username;
|
||||
userEntity = new UserEntity();
|
||||
userEntity.setBaseUrl(importAccount.getBaseUrl());
|
||||
userItems.add(new AdvancedUserItem(participant, userEntity, account));
|
||||
}
|
||||
|
||||
adapter.addListener(onImportItemClickListener);
|
||||
adapter.updateDataSet(userItems, false);
|
||||
}
|
||||
participant.setUserId(userId);
|
||||
userItems.add(new AdvancedUserItem(participant, userEntity, null));
|
||||
}
|
||||
}
|
||||
|
||||
adapter.addListener(onSwitchItemClickListener);
|
||||
adapter.updateDataSet(userItems, false);
|
||||
} else {
|
||||
Account account;
|
||||
ImportAccount importAccount;
|
||||
for (Object accountObject : AccountUtils.INSTANCE.findAccounts(userUtils.getUsers())) {
|
||||
account = (Account) accountObject;
|
||||
importAccount = AccountUtils.INSTANCE.getInformationFromAccount(account);
|
||||
|
||||
participant = new Participant();
|
||||
participant.setName(importAccount.getUsername());
|
||||
participant.setUserId(importAccount.getUsername());
|
||||
userEntity = new UserEntity();
|
||||
userEntity.setBaseUrl(importAccount.getBaseUrl());
|
||||
userItems.add(new AdvancedUserItem(participant, userEntity, account));
|
||||
}
|
||||
|
||||
adapter.addListener(onImportItemClickListener);
|
||||
adapter.updateDataSet(userItems, false);
|
||||
}
|
||||
prepareViews();
|
||||
}
|
||||
|
||||
prepareViews();
|
||||
}
|
||||
private void prepareViews() {
|
||||
LinearLayoutManager layoutManager = new SmoothScrollLinearLayoutManager(getActivity());
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
private void prepareViews() {
|
||||
LinearLayoutManager layoutManager = new SmoothScrollLinearLayoutManager(getActivity());
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
recyclerView.setHasFixedSize(true);
|
||||
recyclerView.setAdapter(adapter);
|
||||
swipeRefreshLayout.setEnabled(false);
|
||||
}
|
||||
|
||||
swipeRefreshLayout.setEnabled(false);
|
||||
}
|
||||
private void reauthorizeFromImport(Account account) {
|
||||
ImportAccount importAccount = AccountUtils.INSTANCE.getInformationFromAccount(account);
|
||||
Bundle bundle = new Bundle();
|
||||
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())
|
||||
.popChangeHandler(new HorizontalChangeHandler()));
|
||||
}
|
||||
|
||||
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.putBoolean(BundleKeys.INSTANCE.getKEY_IS_ACCOUNT_IMPORT(), true);
|
||||
getRouter().pushController(RouterTransaction.with(new AccountVerificationController(bundle))
|
||||
.pushChangeHandler(new HorizontalChangeHandler())
|
||||
.popChangeHandler(new HorizontalChangeHandler()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return getResources().getString(R.string.nc_select_an_account);
|
||||
}
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return getResources().getString(R.string.nc_select_an_account);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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 =
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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])
|
||||
}
|
||||
}
|
@ -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]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
package com.nextcloud.talk.newarch.features.chat
|
||||
|
||||
enum class ChatViewState {
|
||||
INITIAL_LOAD,
|
||||
LOBBY,
|
||||
CHAT,
|
||||
FAILED
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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,
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
@ -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!!
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
24
gradle/wrapper/gradle-wrapper.properties
vendored
24
gradle/wrapper/gradle-wrapper.properties
vendored
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user