Move to compat shortcut manager

This commit is contained in:
Mario Danic 2020-01-02 12:31:47 +01:00 committed by Mario Đanić
parent b265994031
commit d6935d65d6
14 changed files with 217 additions and 144 deletions

View File

@ -191,7 +191,6 @@ dependencies {
implementation "com.github.stateless4j:stateless4j:2.6.0"
// ViewModel and LiveData
implementation "androidx.core:core-ktx:1.1.0"
implementation "androidx.sqlite:sqlite-ktx:2.0.1"
implementation "androidx.collection:collection-ktx:1.1.0"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
@ -229,6 +228,8 @@ dependencies {
androidTestImplementation "androidx.work:work-testing:$work_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.core:core-ktx:1.2.0-rc01'
implementation 'androidx.sharetarget:sharetarget:1.0.0-rc01'
implementation 'com.google.android.material:material:1.2.0-alpha03'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta4'
implementation 'com.github.vanniktech:Emoji:0.6.0'

View File

@ -63,9 +63,9 @@
<application
android:name=".application.NextcloudTalkApplication"
android:allowBackup="false"
android:fullBackupContent="@xml/backup_config"
android:allowClearUserData="false"
android:allowClearUserDataOnFailedRestore="true"
android:fullBackupContent="@xml/backup_config"
android:icon="@mipmap/ic_launcher"
android:label="@string/nc_app_name"
android:largeHeap="true"
@ -79,15 +79,34 @@
android:name="android.max_aspect"
android:value="10" />
<activity
android:name=".activities.MainActivity"
android:label="@string/nc_app_label"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustPan">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!--
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<meta-data android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
<meta-data
android:name="android.service.chooser.chooser_target_service"
android:value="androidx.sharetarget.ChooserTargetServiceCompat"
/>
-->
</activity>
<activity
@ -104,7 +123,6 @@
<service
android:name="com.novoda.merlin.MerlinService"
android:exported="false" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}"

View File

@ -51,7 +51,7 @@ import com.nextcloud.talk.newarch.features.conversationsList.di.module.Conversat
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.*
import com.nextcloud.talk.newarch.utils.ShortcutService
import com.nextcloud.talk.newarch.services.shortcuts.ShortcutService
import com.nextcloud.talk.utils.ClosedInterfaceImpl
import com.nextcloud.talk.utils.DisplayUtils
import com.nextcloud.talk.utils.database.user.UserUtils
@ -133,10 +133,7 @@ class NextcloudTalkApplication : Application(), LifecycleObserver {
setAppTheme(appPreferences.theme)
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val shortcutService: ShortcutService = get()
}
val shortcutService: ShortcutService = get()
Security.insertProviderAt(Conscrypt.newProvider(), 1)
ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync()

View File

@ -664,9 +664,9 @@ class ContactsController : BaseController,
private fun prepareViews() {
layoutManager = SmoothScrollLinearLayoutManager(activity!!)
recyclerView!!.layoutManager = layoutManager
recyclerView!!.setHasFixedSize(true)
recyclerView!!.adapter = adapter
recyclerView?.layoutManager = layoutManager
recyclerView?.setHasFixedSize(true)
recyclerView?.adapter = adapter
adapter!!.setStickyHeaderElevation(5)
.setUnlinkAllItemsOnRemoveHeaders(true)

View File

@ -23,7 +23,6 @@ package com.nextcloud.talk.jobs
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.text.TextUtils
import android.util.Base64
import androidx.work.CoroutineWorker
import androidx.work.ListenableWorker.Result
@ -68,8 +67,8 @@ class PushRegistrationWorker(
}
private fun pushRegistrationToServer() {
val token: String = appPreferences.pushToken
if (!TextUtils.isEmpty(token)) {
val token: String? = appPreferences.pushToken
if (!token.isNullOrEmpty()) {
var credentials: String
val pushUtils = PushUtils(usersRepository)
val pushTokenHash = token.hashWithAlgorithm("SHA-512")

View File

@ -65,8 +65,8 @@ class ConversationsRepositoryImpl(val conversationsDao: ConversationsDao) :
}
}
override fun getLastThreeActiveConversationsForUser(userId: Long): LiveData<List<Conversation>> {
return conversationsDao.getLastThreeConversationsForUser(userId).distinctUntilChanged().map { data ->
override fun getShortcutTargetConversations(userId: Long): LiveData<List<Conversation>> {
return conversationsDao.getShortcutTargetConversations(userId).distinctUntilChanged().map { data ->
data.map {
it.toConversation()
}

View File

@ -6,7 +6,7 @@ import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
import com.nextcloud.talk.newarch.domain.usecases.GetConversationUseCase
import com.nextcloud.talk.newarch.domain.usecases.JoinConversationUseCase
import com.nextcloud.talk.newarch.services.GlobalService
import com.nextcloud.talk.newarch.utils.ShortcutService
import com.nextcloud.talk.newarch.services.shortcuts.ShortcutService
import okhttp3.OkHttpClient
import org.koin.dsl.module
import java.net.CookieManager
@ -14,7 +14,6 @@ import java.net.CookieManager
val ServiceModule = module {
single { createGlobalService(get(), get(), get(), get(), get(), get()) }
single { createShortcutService(get(), get(), get()) }
}
fun createGlobalService(usersRepository: UsersRepository, cookieManager: CookieManager,

View File

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

View File

@ -30,8 +30,8 @@ abstract class ConversationsDao {
@Query("SELECT * FROM conversations WHERE user_id = :userId ORDER BY favorite DESC, last_activity DESC")
abstract fun getConversationsForUser(userId: Long): LiveData<List<ConversationEntity>>
@Query("SELECT * FROM conversations WHERE user_id = :userId ORDER BY favorite DESC, last_activity DESC LIMIT 3")
abstract fun getLastThreeConversationsForUser(userId: Long): LiveData<List<ConversationEntity>>
@Query("SELECT * FROM conversations WHERE user_id = :userId ORDER BY favorite DESC, last_activity DESC LIMIT 4")
abstract fun getShortcutTargetConversations(userId: Long): LiveData<List<ConversationEntity>>
@Query("DELETE FROM conversations WHERE user_id = :userId")
abstract suspend fun clearConversationsForUser(userId: Long)

View File

@ -0,0 +1,149 @@
/*
*
* * Nextcloud Talk application
* *
* * @author Mario Danic
* * Copyright (C) 2017-2020 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.newarch.services.shortcuts
import android.content.Context
import android.content.Intent
import android.graphics.drawable.BitmapDrawable
import androidx.core.app.Person
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import coil.Coil
import coil.api.get
import coil.transform.CircleCropTransformation
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.services.GlobalService
import com.nextcloud.talk.newarch.utils.Images
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlin.math.abs
class ShortcutService constructor(private var context: Context,
private val conversationsRepository: ConversationsRepository,
globalService: GlobalService
) {
private var currentUser: UserNgEntity? = null
private var lastFourActiveConversations: LiveData<List<Conversation>> = Transformations.switchMap(globalService.currentUserLiveData) { user ->
currentUser = user
var internalUserId: Long = -1
currentUser?.let {
internalUserId = it.id!!
}
conversationsRepository.getShortcutTargetConversations(internalUserId)
}
init {
lastFourActiveConversations.observeForever {
GlobalScope.launch {
registerShortcuts(it)
}
}
}
private suspend fun registerShortcuts(conversations: List<Conversation>) {
val openNewConversationIntent = Intent(context, MainActivity::class.java)
openNewConversationIntent.action = BundleKeys.KEY_NEW_CONVERSATION
val shortcuts: MutableList<ShortcutInfoCompat> = mutableListOf()
val contactCategories: MutableSet<String> = HashSet()
contactCategories.add(context.getString(R.string.nc_text_share_target))
val images = Images()
currentUser?.let { user ->
shortcuts.add(ShortcutInfoCompat.Builder(context, "new")
//.setRank(4)
.setShortLabel(context.resources.getString(R.string.nc_new_conversation))
.setIcon(IconCompat.createWithBitmap(context.resources.getDrawable(R.drawable.new_conversation_shortcut).toBitmap()))
.setIntent(openNewConversationIntent)
.setAlwaysBadged()
.build())
for ((index, conversation) in conversations.withIndex()) {
val intent = Intent(context, MainActivity::class.java)
intent.action = BundleKeys.KEY_OPEN_CONVERSATION
intent.putExtra(BundleKeys.KEY_INTERNAL_USER_ID, user.id)
intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversation.token)
val persons = mutableListOf<Person>()
conversation.participants?.forEach {
val hashMap = it.value as HashMap<*, *>
val personBuilder = Person.Builder()
personBuilder.setName(hashMap["name"].toString())
personBuilder.setBot(false)
// we need a key for each of the users
/*val isGuest = hashMap["type"]?.equals(Participant.ParticipantType.GUEST) == true
|| hashMap["type"]?.equals(Participant.ParticipantType.GUEST_AS_MODERATOR) == true
|| hashMap["type"]?.equals(Participant.ParticipantType.USER_FOLLOWING_LINK) == true
val avatarUrl = if (isGuest) ApiUtils.getUrlForAvatarWithNameForGuests(user.baseUrl, hashMap["name"].toString(), R.dimen.avatar_size_big)
else ApiUtils.getUrlForAvatarWithName(user.baseUrl, hashMap["userId"].toString(), R.dimen.avatar_size_big)
iconImage = Coil.get(avatarUrl) {
addHeader("Authorization", user.getCredentials())
transformations(CircleCropTransformation())
}
personBuilder.setIcon(IconCompat.createWithBitmap((iconImage as BitmapDrawable).bitmap))*/
persons.add(personBuilder.build())
}
var iconImage = images.getImageForConversation(context, conversation)
if (iconImage == null) {
iconImage = Coil.get(ApiUtils.getUrlForAvatarWithName(user.baseUrl, conversation.name, R.dimen.avatar_size_big)) {
addHeader("Authorization", user.getCredentials())
transformations(CircleCropTransformation())
}
}
shortcuts.add(ShortcutInfoCompat.Builder(context, "current_conversation_" + (index + 1))
.setShortLabel(conversation.displayName as String)
.setLongLabel(conversation.displayName as String)
.setIcon(IconCompat.createWithBitmap((iconImage as BitmapDrawable).bitmap))
.setIntent(intent)
.setRank(abs(index - 4 + 1))
.setRank(0)
.setAlwaysBadged()
.setCategories(contactCategories)
.setPersons(persons.toTypedArray())
.build())
}
}
ShortcutManagerCompat.removeAllDynamicShortcuts(context)
ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts)
}
}

View File

@ -1,123 +0,0 @@
/*
* Nextcloud Talk application
*
* @author Thomas Ebert<thomas@thomasebert.net>
* @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/>.
*/
package com.nextcloud.talk.newarch.utils
import android.annotation.TargetApi
import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Icon
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.core.graphics.drawable.toBitmap
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import coil.Coil
import coil.api.get
import coil.transform.CircleCropTransformation
import com.nextcloud.talk.R
import com.nextcloud.talk.activities.MainActivity
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.newarch.domain.repository.offline.ConversationsRepository
import com.nextcloud.talk.newarch.local.models.UserNgEntity
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.services.GlobalService
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.bundle.BundleKeys
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class ShortcutService constructor(private val context: Context,
private val conversationsRepository: ConversationsRepository,
conversationsService: GlobalService
) {
private var currentUser: UserNgEntity? = null
@RequiresApi(Build.VERSION_CODES.N_MR1)
private val shortcutManager = context.getSystemService(ShortcutManager::class.java)
@RequiresApi(Build.VERSION_CODES.P)
private var lastThreeActiveConversations: LiveData<List<Conversation>> = Transformations.switchMap(conversationsService.currentUserLiveData) { user ->
currentUser = user
var internalUserId: Long = -1
currentUser?.let {
internalUserId = it.id!!
}
conversationsRepository.getLastThreeActiveConversationsForUser(internalUserId)
}
init {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
lastThreeActiveConversations.observeForever {
GlobalScope.launch {
registerShortcuts()
}
}
}
}
@TargetApi(Build.VERSION_CODES.P)
private suspend fun registerShortcuts() {
val openNewConversationIntent = Intent(context, MainActivity::class.java)
openNewConversationIntent.action = BundleKeys.KEY_NEW_CONVERSATION
val shortcuts: MutableList<ShortcutInfo> = mutableListOf()
val images = Images()
currentUser?.let { user ->
shortcuts.add(ShortcutInfo.Builder(context, "new")
.setShortLabel(context.resources.getString(R.string.nc_new_conversation_short))
.setLongLabel(context.resources.getString(R.string.nc_new_conversation))
.setIcon(Icon.createWithBitmap(context.resources.getDrawable(R.drawable.new_conversation_shortcut).toBitmap()))
.setIntent(openNewConversationIntent)
.build())
lastThreeActiveConversations.value?.let { conversations ->
for ((index, conversation) in conversations.withIndex()) {
val intent = Intent(context, MainActivity::class.java)
intent.action = BundleKeys.KEY_OPEN_CONVERSATION
intent.putExtra(BundleKeys.KEY_INTERNAL_USER_ID, user.id)
intent.putExtra(BundleKeys.KEY_ROOM_TOKEN, conversation.token)
var iconImage = images.getImageForConversation(context, conversation)
if (iconImage == null) {
iconImage = Coil.get(ApiUtils.getUrlForAvatarWithName(user.baseUrl, conversation.name, R.dimen.avatar_size_big)) {
addHeader("Authorization", user.getCredentials())
transformations(CircleCropTransformation())
}
}
shortcuts.add(ShortcutInfo.Builder(context, "current_conversation_" + (index + 1))
.setShortLabel(conversation.displayName as String)
.setLongLabel(conversation.displayName as String)
.setIcon(Icon.createWithBitmap((iconImage as BitmapDrawable).bitmap))
.setIntent(intent)
.build())
}
}
}
shortcutManager?.dynamicShortcuts = shortcuts
}
}

View File

@ -60,4 +60,5 @@ object BundleKeys {
val KEY_ACCOUNT = "KEY_ACCOUNT"
val KEY_FILE_ID = "KEY_FILE_ID"
val KEY_NOTIFICATION_ID = "KEY_NOTIFICATION_ID"
val KEY_CONVERSATION_ID = "KEY_CONVERSATION_ID"
}

View File

@ -50,4 +50,7 @@
<string name="google_app_id" translatable="false">1:829118773643:android:54b65087c544d819</string>
<string name="google_crash_reporting_api_key" translatable="false">AIzaSyAWIyOcLafaFp8PFL61h64cy1NNZW2cU_s</string>
<string name="google_storage_bucket" translatable="false">nextcloud-a7dea.appspot.com</string>
<!-- Other things -->
<string name="nc_text_share_target">com.nextcloud.talk.newarch.services.shortcuts.category.TEXT_SHARE_TARGET</string>
</resources>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ /*
~ * Nextcloud Talk application
~ *
~ * @author Mario Danic
~ * Copyright (C) 2017-2020 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/>.
~ */
-->
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<share-target android:targetClass="com.nextcloud.talk.activities.MainActivity" >
<data android:mimeType="text/plain" />
<category android:name="@string/nc_text_share_target" android:label="@string/nc_app_label"/>
</share-target>
</shortcuts>