mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-20 12:09:45 +01:00
Lots of progress on the conversations list view
Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
parent
ab3cb83cdf
commit
8a3008ef25
@ -76,6 +76,10 @@ android {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
dataBinding {
|
||||
enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
dexOptions {
|
||||
@ -136,8 +140,9 @@ android {
|
||||
}
|
||||
|
||||
ext {
|
||||
workVersion = "1.0.1"
|
||||
koin_version = "2.0.1"
|
||||
work_version = "1.0.1"
|
||||
koin_version = "2.1.0-alpha-1"
|
||||
lifecycle_version = "2.1.0"
|
||||
}
|
||||
|
||||
|
||||
@ -161,6 +166,25 @@ dependencies {
|
||||
implementation "org.koin:koin-androidx-ext:$koin_version"
|
||||
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2'
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
|
||||
implementation "com.github.stateless4j:stateless4j:2.6.0"
|
||||
|
||||
// ViewModel and LiveData
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
|
||||
implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-beta01"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-beta01"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-beta01"
|
||||
|
||||
// optional - ReactiveStreams support for LiveData
|
||||
implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version" // For Kotlin use lifecycle-reactivestreams-ktx
|
||||
|
||||
// optional - Test helpers for LiveData
|
||||
testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
implementation 'com.google.android.material:material:1.1.0-beta01'
|
||||
@ -168,10 +192,10 @@ dependencies {
|
||||
implementation 'com.github.vanniktech:Emoji:0.6.0'
|
||||
implementation group: 'androidx.emoji', name: 'emoji-bundled', version: '1.0.0'
|
||||
implementation 'org.michaelevans.colorart:library:0.0.3'
|
||||
implementation "android.arch.work:work-runtime:${workVersion}"
|
||||
implementation "android.arch.work:work-rxjava2:${workVersion}"
|
||||
implementation "android.arch.work:work-runtime:${work_version}"
|
||||
implementation "android.arch.work:work-rxjava2:${work_version}"
|
||||
implementation 'com.google.android:flexbox:1.1.0'
|
||||
androidTestImplementation "android.arch.work:work-testing:${workVersion}"
|
||||
androidTestImplementation "android.arch.work:work-testing:${work_version}"
|
||||
implementation ('com.gitlab.bitfireAT:dav4jvm:f2078bc846', {
|
||||
exclude group: 'org.ogce', module: 'xpp3' // Android comes with its own XmlPullParser
|
||||
})
|
||||
@ -179,7 +203,7 @@ dependencies {
|
||||
|
||||
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
|
||||
|
||||
implementation 'androidx.biometric:biometric:1.0.0-beta02'
|
||||
implementation 'androidx.biometric:biometric:1.0.0-rc01'
|
||||
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
|
||||
|
||||
implementation 'androidx.multidex:multidex:2.0.1'
|
||||
@ -192,6 +216,7 @@ dependencies {
|
||||
implementation 'com.bluelinelabs:conductor-archlifecycle:3.0.0-rc2'
|
||||
implementation 'com.bluelinelabs:conductor-rxlifecycle2:3.0.0-rc2'
|
||||
implementation 'com.bluelinelabs:conductor-autodispose:3.0.0-rc2'
|
||||
implementation "com.github.miquelbeltran:conductor-viewmodel:1.0.3"
|
||||
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.2.2'
|
||||
implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.2.2'
|
||||
@ -226,6 +251,7 @@ dependencies {
|
||||
implementation 'com.github.HITGIF:TextFieldBoxes:1.4.5'
|
||||
implementation 'eu.davidea:flexible-adapter:5.1.0'
|
||||
implementation 'eu.davidea:flexible-adapter-ui:1.0.0'
|
||||
implementation 'eu.davidea:flexible-adapter-livedata:1.0.0-b3'
|
||||
implementation 'org.webrtc:google-webrtc:1.0.23295'
|
||||
implementation 'com.yarolegovich:lovely-dialog:1.1.0'
|
||||
implementation 'com.yarolegovich:lovelyinput:1.0.9'
|
||||
|
@ -43,6 +43,7 @@ import com.nextcloud.talk.controllers.ConversationsListController
|
||||
import com.nextcloud.talk.controllers.LockedController
|
||||
import com.nextcloud.talk.controllers.ServerSelectionController
|
||||
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
|
||||
import com.nextcloud.talk.newarch.features.conversationsList.ConversationsListView
|
||||
import com.nextcloud.talk.utils.ConductorRemapping
|
||||
import com.nextcloud.talk.utils.SecurityUtils
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
@ -91,7 +92,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
||||
|
||||
if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
|
||||
if (!router!!.hasRootController()) {
|
||||
router!!.setRoot(RouterTransaction.with(ConversationsListController())
|
||||
router!!.setRoot(RouterTransaction.with(ConversationsListView())
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
}
|
||||
@ -99,7 +100,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
||||
} else if (!router!!.hasRootController()) {
|
||||
if (hasDb) {
|
||||
if (userUtils.anyUserExists()) {
|
||||
router!!.setRoot(RouterTransaction.with(ConversationsListController())
|
||||
router!!.setRoot(RouterTransaction.with(ConversationsListView())
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
} else {
|
||||
|
@ -60,7 +60,7 @@ public class ConversationItem extends AbstractFlexibleItem<ConversationItem.Conv
|
||||
private Context context;
|
||||
|
||||
public ConversationItem(Conversation conversation, UserEntity userEntity,
|
||||
Context activityContext) {
|
||||
Context activityContext) {
|
||||
this.conversation = conversation;
|
||||
this.userEntity = userEntity;
|
||||
this.context = activityContext;
|
||||
@ -98,6 +98,7 @@ public class ConversationItem extends AbstractFlexibleItem<ConversationItem.Conv
|
||||
public void bindViewHolder(FlexibleAdapter<IFlexible> adapter, ConversationItemViewHolder holder, int position, List<Object> payloads) {
|
||||
Context appContext =
|
||||
NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext();
|
||||
|
||||
holder.dialogAvatar.setController(null);
|
||||
|
||||
if (adapter.hasFilter()) {
|
||||
|
@ -319,7 +319,7 @@ public interface NcApi {
|
||||
|
||||
@FormUrlEncoded
|
||||
@PUT
|
||||
Observable<GenericOverall> setReadOnlyState(@Header("Authorization") String authorization, @Url String url, @Field("state") int state);
|
||||
Observable<GenericOverall> setReadOnlyState(@Header("Authorization") String authorization, @Url String url, @Field("viewState") int state);
|
||||
|
||||
|
||||
@FormUrlEncoded
|
||||
@ -332,7 +332,7 @@ public interface NcApi {
|
||||
@FormUrlEncoded
|
||||
@PUT
|
||||
Observable<GenericOverall> setLobbyForConversation(@Header("Authorization") String authorization,
|
||||
@Url String url, @Field("state") Integer state,
|
||||
@Url String url, @Field("viewState") Integer state,
|
||||
@Field("timer") Long timer);
|
||||
|
||||
}
|
||||
|
@ -52,6 +52,7 @@ import com.nextcloud.talk.jobs.SignalingSettingsWorker
|
||||
import com.nextcloud.talk.newarch.di.module.CommunicationModule
|
||||
import com.nextcloud.talk.newarch.di.module.NetworkModule
|
||||
import com.nextcloud.talk.newarch.di.module.StorageModule
|
||||
import com.nextcloud.talk.newarch.features.conversationsList.di.module.ConversationsListModule
|
||||
import com.nextcloud.talk.utils.ClosedInterfaceImpl
|
||||
import com.nextcloud.talk.utils.DeviceUtils
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
@ -193,7 +194,7 @@ class NextcloudTalkApplication : Application(), LifecycleObserver {
|
||||
startKoin {
|
||||
androidContext(this@NextcloudTalkApplication)
|
||||
androidLogger()
|
||||
modules(listOf(CommunicationModule, StorageModule, NetworkModule))
|
||||
modules(listOf(CommunicationModule, StorageModule, NetworkModule, ConversationsListModule))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ public class BrowserController extends BaseController implements ListingInterfac
|
||||
private final Set<String> selectedPaths;
|
||||
@Inject
|
||||
UserUtils userUtils;
|
||||
@BindView(R.id.recycler_view)
|
||||
@BindView(R.id.recyclerView)
|
||||
RecyclerView recyclerView;
|
||||
@BindView(R.id.fast_scroller)
|
||||
FastScroller fastScroller;
|
||||
|
@ -84,6 +84,9 @@ public class AccountVerificationController extends BaseController {
|
||||
@Inject
|
||||
AppPreferences appPreferences;
|
||||
|
||||
@Inject
|
||||
EventBus eventBus;
|
||||
|
||||
@BindView(R.id.progress_text)
|
||||
TextView progressText;
|
||||
|
||||
@ -115,6 +118,18 @@ public class AccountVerificationController extends BaseController {
|
||||
return inflater.inflate(R.layout.controller_account_verification, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetach(@NonNull View view) {
|
||||
eventBus.unregister(this);
|
||||
super.onDetach(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttach(@NonNull View view) {
|
||||
super.onAttach(view);
|
||||
eventBus.register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
|
@ -197,6 +197,8 @@ public class CallController extends BaseController {
|
||||
AppPreferences appPreferences;
|
||||
@Inject
|
||||
Cache cache;
|
||||
@Inject
|
||||
EventBus eventBus;
|
||||
|
||||
private PeerConnectionFactory peerConnectionFactory;
|
||||
private MediaConstraints audioConstraints;
|
||||
@ -1239,6 +1241,18 @@ public class CallController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetach(@NonNull View view) {
|
||||
eventBus.unregister(this);
|
||||
super.onDetach(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttach(@NonNull View view) {
|
||||
super.onAttach(view);
|
||||
eventBus.register(this);
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.BACKGROUND)
|
||||
public void onMessageEvent(WebSocketCommunicationEvent webSocketCommunicationEvent) {
|
||||
switch (webSocketCommunicationEvent.getType()) {
|
||||
|
@ -160,6 +160,18 @@ public class CallNotificationController extends BaseController {
|
||||
return inflater.inflate(R.layout.controller_call_notification, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetach(@NonNull View view) {
|
||||
eventBus.unregister(this);
|
||||
super.onDetach(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttach(@NonNull View view) {
|
||||
super.onAttach(view);
|
||||
eventBus.register(this);
|
||||
}
|
||||
|
||||
private void showAnswerControls() {
|
||||
callAnswerCameraView.setVisibility(View.VISIBLE);
|
||||
callAnswerVoiceOnlyView.setVisibility(View.VISIBLE);
|
||||
|
@ -654,6 +654,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
eventBus.register(this)
|
||||
|
||||
if (conversationUser?.userId != "?" && conversationUser?.hasSpreedFeatureCapability(
|
||||
"mention-flag"
|
||||
@ -728,7 +729,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
||||
}
|
||||
|
||||
override fun onDetach(view: View) {
|
||||
super.onDetach(view)
|
||||
eventBus.unregister(this)
|
||||
ApplicationWideCurrentRoomHolder.getInstance()
|
||||
.clear()
|
||||
|
||||
@ -747,6 +748,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
||||
if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) {
|
||||
mentionAutocomplete?.dismissPopup()
|
||||
}
|
||||
|
||||
super.onDetach(view)
|
||||
}
|
||||
|
||||
override fun getTitle(): String? {
|
||||
|
@ -128,7 +128,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
|
||||
AppPreferences appPreferences;
|
||||
@BindView(R.id.progressBar)
|
||||
ProgressBar progressBar;
|
||||
@BindView(R.id.recycler_view)
|
||||
@BindView(R.id.recyclerView)
|
||||
RecyclerView recyclerView;
|
||||
|
||||
@BindView(R.id.swipe_refresh_layout)
|
||||
@ -210,9 +210,16 @@ public class ContactsController extends BaseController implements SearchView.OnQ
|
||||
return inflater.inflate(R.layout.controller_contacts_rv, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetach(@NonNull View view) {
|
||||
eventBus.unregister(this);
|
||||
super.onDetach(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttach(@NonNull View view) {
|
||||
super.onAttach(view);
|
||||
eventBus.register(this);
|
||||
|
||||
if (isNewConversationView) {
|
||||
toggleNewCallHeaderVisibility(!isPublicCall);
|
||||
|
@ -20,7 +20,7 @@
|
||||
|
||||
package com.nextcloud.talk.controllers
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.os.Bundle
|
||||
@ -85,7 +85,6 @@ import io.reactivex.Observer
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.util.ArrayList
|
||||
@ -94,7 +93,6 @@ import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class ConversationInfoController(args: Bundle) : BaseController(), FlexibleAdapter.OnItemClickListener {
|
||||
|
||||
@BindView(R.id.notification_settings)
|
||||
lateinit var notificationsPreferenceScreen: MaterialPreferenceScreen
|
||||
@BindView(R.id.progressBar)
|
||||
@ -115,7 +113,7 @@ class ConversationInfoController(args: Bundle) : BaseController(), FlexibleAdapt
|
||||
lateinit var conversationDisplayName: EmojiTextView
|
||||
@BindView(R.id.participants_list_category)
|
||||
lateinit var participantsListCategory: MaterialPreferenceCategoryWithRightLink
|
||||
@BindView(R.id.recycler_view)
|
||||
@BindView(R.id.recyclerView)
|
||||
lateinit var recyclerView: RecyclerView
|
||||
@BindView(R.id.deleteConversationAction)
|
||||
lateinit var deleteConversationAction: MaterialStandardPreference
|
||||
@ -179,8 +177,14 @@ class ConversationInfoController(args: Bundle) : BaseController(), FlexibleAdapt
|
||||
return inflater.inflate(R.layout.controller_conversation_info, container, false)
|
||||
}
|
||||
|
||||
override fun onDetach(view: View) {
|
||||
eventBus.unregister(this)
|
||||
super.onDetach(view)
|
||||
}
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
eventBus.register(this)
|
||||
|
||||
if (databaseStorageModule == null) {
|
||||
databaseStorageModule = DatabaseStorageModule(conversationUser!!, conversationToken)
|
||||
@ -327,7 +331,7 @@ class ConversationInfoController(args: Bundle) : BaseController(), FlexibleAdapt
|
||||
super.onRestoreViewState(view, savedViewState)
|
||||
if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) {
|
||||
//Dialog won't be restarted automatically, so we need to call this method.
|
||||
//Each dialog knows how to restore its state
|
||||
//Each dialog knows how to restore its viewState
|
||||
showLovelyDialog(LovelySaveStateHandler.getSavedDialogId(savedViewState), savedViewState)
|
||||
}
|
||||
}
|
||||
|
@ -133,7 +133,7 @@ public class ConversationsListController extends BaseController implements Searc
|
||||
@Inject
|
||||
AppPreferences appPreferences;
|
||||
|
||||
@BindView(R.id.recycler_view)
|
||||
@BindView(R.id.recyclerView)
|
||||
RecyclerView recyclerView;
|
||||
|
||||
@BindView(R.id.swipeRefreshLayoutView)
|
||||
@ -480,7 +480,7 @@ public class ConversationsListController extends BaseController implements Searc
|
||||
searchQuery = savedViewState.getString(KEY_SEARCH_QUERY, "");
|
||||
if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) {
|
||||
//Dialog won't be restarted automatically, so we need to call this method.
|
||||
//Each dialog knows how to restore its state
|
||||
//Each dialog knows how to restore its viewState
|
||||
showLovelyDialog(LovelySaveStateHandler.getSavedDialogId(savedViewState), savedViewState);
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ public class RingtoneSelectionController extends BaseController implements Flexi
|
||||
|
||||
private static final String TAG = "RingtoneSelectionController";
|
||||
|
||||
@BindView(R.id.recycler_view)
|
||||
@BindView(R.id.recyclerView)
|
||||
RecyclerView recyclerView;
|
||||
|
||||
@BindView(R.id.swipe_refresh_layout)
|
||||
|
@ -346,7 +346,7 @@ public class SettingsController extends BaseController {
|
||||
super.onRestoreViewState(view, savedViewState);
|
||||
if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) {
|
||||
//Dialog won't be restarted automatically, so we need to call this method.
|
||||
//Each dialog knows how to restore its state
|
||||
//Each dialog knows how to restore its viewState
|
||||
showLovelyDialog(LovelySaveStateHandler.getSavedDialogId(savedViewState), savedViewState);
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ public class SwitchAccountController extends BaseController {
|
||||
@Inject
|
||||
UserUtils userUtils;
|
||||
|
||||
@BindView(R.id.recycler_view)
|
||||
@BindView(R.id.recyclerView)
|
||||
RecyclerView recyclerView;
|
||||
|
||||
@Inject
|
||||
|
@ -21,10 +21,11 @@
|
||||
*/
|
||||
package com.nextcloud.talk.controllers.base
|
||||
|
||||
import android.content.ComponentCallbacks
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -44,16 +45,11 @@ import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import com.uber.autodispose.lifecycle.LifecycleScopeProvider
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.koin.core.Koin
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.core.scope.Scope
|
||||
import org.koin.android.ext.android.inject
|
||||
import java.util.ArrayList
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
abstract class BaseController : ButterKnifeController(), KoinComponent {
|
||||
abstract class BaseController : ButterKnifeController(), ComponentCallbacks {
|
||||
|
||||
val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this)
|
||||
|
||||
@ -75,10 +71,6 @@ abstract class BaseController : ButterKnifeController(), KoinComponent {
|
||||
return actionBarProvider?.supportActionBar
|
||||
}
|
||||
|
||||
override fun getKoin(): Koin {
|
||||
return super.getKoin()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
@ -112,7 +104,6 @@ abstract class BaseController : ButterKnifeController(), KoinComponent {
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
eventBus.register(this)
|
||||
|
||||
setTitle()
|
||||
if (actionBar != null) {
|
||||
@ -121,7 +112,6 @@ abstract class BaseController : ButterKnifeController(), KoinComponent {
|
||||
}
|
||||
|
||||
override fun onDetach(view: View) {
|
||||
eventBus.unregister(this)
|
||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||
super.onDetach(view)
|
||||
@ -159,6 +149,12 @@ abstract class BaseController : ButterKnifeController(), KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLowMemory() {
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = "BaseController"
|
||||
|
@ -31,7 +31,7 @@ import com.bluelinelabs.conductor.Controller
|
||||
|
||||
abstract class ButterKnifeController : Controller {
|
||||
|
||||
private var unbinder: Unbinder? = null
|
||||
protected var unbinder: Unbinder? = null
|
||||
|
||||
constructor() {}
|
||||
|
||||
|
@ -67,7 +67,7 @@ import org.parceler.Parcels;
|
||||
@AutoInjector(NextcloudTalkApplication.class)
|
||||
public class CallMenuController extends BaseController
|
||||
implements FlexibleAdapter.OnItemClickListener {
|
||||
@BindView(R.id.recycler_view)
|
||||
@BindView(R.id.recyclerView)
|
||||
RecyclerView recyclerView;
|
||||
|
||||
@Inject
|
||||
|
@ -27,8 +27,8 @@ import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
|
||||
class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : NextcloudTalkRepository {
|
||||
override suspend fun getConversations(user: UserEntity): List<Conversation> {
|
||||
override suspend fun getConversationsForUser(user: UserEntity): List<Conversation> {
|
||||
return apiService.getConversations(ApiUtils.getCredentials(user.username, user.token),
|
||||
ApiUtils.getUrlForGetRooms(user.baseUrl)).ocs.data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ import okhttp3.OkHttpClient
|
||||
import okhttp3.internal.tls.OkHostnameVerifier
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import okhttp3.logging.HttpLoggingInterceptor.Logger
|
||||
import org.koin.android.ext.koin.androidApplication
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
import retrofit2.Retrofit
|
||||
@ -70,8 +71,16 @@ import javax.net.ssl.X509KeyManager
|
||||
val NetworkModule = module {
|
||||
single { createService(get()) }
|
||||
single { createRetrofit(get()) }
|
||||
single { createProxy(get()) }
|
||||
single { createTrustManager() }
|
||||
single { createCookieManager() }
|
||||
single { createDispatcher() }
|
||||
single { createKeyManager(get(), get()) }
|
||||
single { createSslSocketFactory(get(), get()) }
|
||||
single { createCache(androidApplication() as NextcloudTalkApplication) }
|
||||
single { createOkHttpClient(androidContext(), get(), get(), get(), get(), get(), get(), get()) }
|
||||
factory { createGetConversationsUseCase(get(), get()) }
|
||||
|
||||
single { createNextcloudTalkRepository(get()) }
|
||||
}
|
||||
|
||||
fun createCookieManager(): CookieManager {
|
||||
@ -242,9 +251,3 @@ fun createNextcloudTalkRepository(apiService: ApiService): NextcloudTalkReposito
|
||||
return NextcloudTalkRepositoryImpl(apiService)
|
||||
}
|
||||
|
||||
fun createGetConversationsUseCase(
|
||||
nextcloudTalkRepository: NextcloudTalkRepository,
|
||||
apiErrorHandler: ApiErrorHandler
|
||||
): GetConversationsUseCase {
|
||||
return GetConversationsUseCase(nextcloudTalkRepository, apiErrorHandler)
|
||||
}
|
||||
|
@ -24,5 +24,5 @@ import com.nextcloud.talk.models.database.UserEntity
|
||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
|
||||
interface NextcloudTalkRepository {
|
||||
suspend fun getConversations(user: UserEntity): List<Conversation>
|
||||
suspend fun getConversationsForUser(user: UserEntity): List<Conversation>
|
||||
}
|
||||
|
@ -31,6 +31,6 @@ class GetConversationsUseCase constructor(
|
||||
) : UseCase<List<Conversation>, Any?>(apiErrorHandler) {
|
||||
|
||||
override suspend fun run(params: Any?): List<Conversation> {
|
||||
return nextcloudTalkRepository.getConversations(user);
|
||||
return nextcloudTalkRepository.getConversationsForUser(user);
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ abstract class UseCase<Type, in Params>(private val apiErrorHandler: ApiErrorHan
|
||||
|
||||
abstract suspend fun run(params: Params? = null): Type
|
||||
lateinit var user: UserEntity
|
||||
fun isUserInitialized() = ::user.isInitialized
|
||||
|
||||
fun invoke(
|
||||
scope: CoroutineScope,
|
||||
|
@ -18,29 +18,19 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.newarch.conversationsList.mvp
|
||||
package com.nextcloud.talk.newarch.features.conversationsList
|
||||
|
||||
import android.view.View
|
||||
import androidx.annotation.LayoutRes
|
||||
import autodagger.AutoInjector
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.controllers.base.BaseController
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
|
||||
import com.nextcloud.talk.utils.database.user.UserUtils
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
abstract class BaseView : BaseController() {
|
||||
class ConversationListViewModelFactory constructor(
|
||||
private val conversationsUseCase: GetConversationsUseCase,
|
||||
private val userUtils: UserUtils
|
||||
): ViewModelProvider.Factory {
|
||||
|
||||
override fun onDetach(view: View) {
|
||||
super.onDetach(view)
|
||||
getPresenter().stop()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
getPresenter().destroy()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
protected abstract fun getLayoutId(): Int
|
||||
|
||||
protected abstract fun getPresenter(): MvpPresenter
|
||||
}
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return ConversationsListViewModel(conversationsUseCase, userUtils) as T;
|
||||
}
|
||||
}
|
@ -0,0 +1,335 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.newarch.features.conversationsList
|
||||
|
||||
import android.app.SearchManager
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.InputType
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.appcompat.widget.SearchView
|
||||
import androidx.appcompat.widget.SearchView.OnQueryTextListener
|
||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory
|
||||
import androidx.core.view.MenuItemCompat
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import butterknife.OnClick
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.bluelinelabs.conductor.changehandler.TransitionChangeHandlerCompat
|
||||
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
|
||||
import com.facebook.common.executors.UiThreadImmediateExecutorService
|
||||
import com.facebook.common.references.CloseableReference
|
||||
import com.facebook.datasource.DataSource
|
||||
import com.facebook.drawee.backends.pipeline.Fresco
|
||||
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
|
||||
import com.facebook.imagepipeline.image.CloseableImage
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.adapters.items.ConversationItem
|
||||
import com.nextcloud.talk.controllers.ContactsController
|
||||
import com.nextcloud.talk.controllers.SettingsController
|
||||
import com.nextcloud.talk.controllers.bottomsheet.CallMenuController.MenuType
|
||||
import com.nextcloud.talk.controllers.bottomsheet.CallMenuController.MenuType.REGULAR
|
||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.FAILED
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADING
|
||||
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.ConductorRemapping
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.nextcloud.talk.utils.animations.SharedElementTransition
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemClickListener
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter.OnItemLongClickListener
|
||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import kotlinx.android.synthetic.main.controller_conversations_rv.view.emptyLayout
|
||||
import kotlinx.android.synthetic.main.controller_conversations_rv.view.floatingActionButton
|
||||
import kotlinx.android.synthetic.main.controller_conversations_rv.view.progressBar
|
||||
import kotlinx.android.synthetic.main.controller_conversations_rv.view.recyclerView
|
||||
import kotlinx.android.synthetic.main.controller_conversations_rv.view.swipeRefreshLayoutView
|
||||
import kotlinx.android.synthetic.main.fast_scroller.view.fast_scroller
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.parceler.Parcels
|
||||
import java.util.ArrayList
|
||||
|
||||
class ConversationsListView() : BaseView(), OnQueryTextListener,
|
||||
OnItemClickListener, OnItemLongClickListener {
|
||||
|
||||
lateinit var viewModel: ConversationsListViewModel
|
||||
val factory: ConversationListViewModelFactory by inject()
|
||||
|
||||
private val recyclerViewAdapter = FlexibleAdapter(mutableListOf())
|
||||
|
||||
private var searchItem: MenuItem? = null
|
||||
private var searchView: SearchView? = null
|
||||
|
||||
override fun onCreateOptionsMenu(
|
||||
menu: Menu,
|
||||
inflater: MenuInflater
|
||||
) {
|
||||
super.onCreateOptionsMenu(menu, inflater)
|
||||
inflater.inflate(R.menu.menu_conversation_plus_filter, menu)
|
||||
searchItem = menu.findItem(R.id.action_search)
|
||||
initSearchView()
|
||||
}
|
||||
|
||||
override fun onPrepareOptionsMenu(menu: Menu) {
|
||||
super.onPrepareOptionsMenu(menu)
|
||||
if (recyclerViewAdapter.hasFilter()) {
|
||||
searchItem?.expandActionView()
|
||||
searchView?.setQuery(viewModel.searchQuery.value, false)
|
||||
recyclerViewAdapter.filterItems()
|
||||
}
|
||||
|
||||
loadUserAvatar(menu.findItem(R.id.action_settings))
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
when (item.itemId) {
|
||||
R.id.action_settings -> {
|
||||
val names = ArrayList<String>()
|
||||
names.add("userAvatar.transitionTag")
|
||||
router.pushController(
|
||||
RouterTransaction.with(SettingsController())
|
||||
.pushChangeHandler(
|
||||
TransitionChangeHandlerCompat(
|
||||
SharedElementTransition(names), VerticalChangeHandler()
|
||||
)
|
||||
)
|
||||
.popChangeHandler(
|
||||
TransitionChangeHandlerCompat(
|
||||
SharedElementTransition(names), VerticalChangeHandler()
|
||||
)
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
else -> return super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initSearchView() {
|
||||
val searchManager = activity!!.getSystemService(Context.SEARCH_SERVICE) as SearchManager
|
||||
searchView = MenuItemCompat.getActionView(searchItem) as SearchView
|
||||
searchView!!.setMaxWidth(Integer.MAX_VALUE)
|
||||
searchView!!.setInputType(InputType.TYPE_TEXT_VARIATION_FILTER)
|
||||
var imeOptions = EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_FULLSCREEN
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.isKeyboardIncognito) {
|
||||
imeOptions = imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
|
||||
}
|
||||
searchView!!.setImeOptions(imeOptions)
|
||||
searchView!!.setQueryHint(resources!!.getString(R.string.nc_search))
|
||||
searchView!!.setSearchableInfo(searchManager.getSearchableInfo(activity!!.componentName))
|
||||
|
||||
searchView!!.setOnQueryTextListener(this)
|
||||
|
||||
}
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
if (!viewModel.searchQuery.value.equals(query)) {
|
||||
viewModel.searchQuery.value = query
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
return onQueryTextSubmit(newText)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup
|
||||
): View {
|
||||
setHasOptionsMenu(true)
|
||||
|
||||
viewModel = viewModelProvider(factory).get(ConversationsListViewModel::class.java)
|
||||
viewModel.apply {
|
||||
viewState.observe(this@ConversationsListView, Observer { value ->
|
||||
when (value) {
|
||||
LOADING -> {
|
||||
view?.recyclerView?.visibility = View.GONE
|
||||
view?.emptyLayout?.visibility = View.GONE
|
||||
view?.swipeRefreshLayoutView?.visibility = View.GONE
|
||||
view?.progressBar?.visibility = View.VISIBLE
|
||||
view?.floatingActionButton?.visibility = View.GONE
|
||||
searchItem?.setVisible(false)
|
||||
}
|
||||
LOADED, FAILED -> {
|
||||
view?.recyclerView?.visibility = View.VISIBLE
|
||||
// The rest is handled in an actual network call
|
||||
view?.progressBar?.visibility = View.GONE
|
||||
view?.floatingActionButton?.visibility = View.VISIBLE
|
||||
}
|
||||
else -> {
|
||||
// We should not be here
|
||||
}
|
||||
}
|
||||
|
||||
searchQuery.observe(this@ConversationsListView, Observer {
|
||||
recyclerViewAdapter.setFilter(it)
|
||||
recyclerViewAdapter.filterItems(500)
|
||||
})
|
||||
|
||||
conversationsListData.observe(this@ConversationsListView, Observer {
|
||||
val newConversations = mutableListOf<ConversationItem>()
|
||||
for (conversation in it) {
|
||||
newConversations.add(ConversationItem(conversation, viewModel.currentUser, activity))
|
||||
}
|
||||
|
||||
recyclerViewAdapter.updateDataSet(newConversations as List<IFlexible<ViewHolder>>?)
|
||||
|
||||
if (it.isNotEmpty()) {
|
||||
view?.emptyLayout?.visibility = View.GONE
|
||||
view?.swipeRefreshLayoutView?.visibility = View.VISIBLE
|
||||
searchItem?.setVisible(true)
|
||||
} else {
|
||||
view?.emptyLayout?.visibility = View.VISIBLE
|
||||
view?.swipeRefreshLayoutView?.visibility = View.GONE
|
||||
searchItem?.setVisible(false)
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
return super.onCreateView(inflater, container)
|
||||
}
|
||||
|
||||
private fun loadUserAvatar(menuItem: MenuItem) {
|
||||
if (activity != null) {
|
||||
val avatarSize =
|
||||
DisplayUtils.convertDpToPixel(menuItem.icon.intrinsicHeight.toFloat(), activity!!)
|
||||
.toInt()
|
||||
val imageRequest = DisplayUtils.getImageRequestForUrl(
|
||||
ApiUtils.getUrlForAvatarWithNameAndPixels(
|
||||
viewModel.currentUser.baseUrl,
|
||||
viewModel.currentUser.userId, avatarSize
|
||||
), null
|
||||
)
|
||||
|
||||
val imagePipeline = Fresco.getImagePipeline()
|
||||
val dataSource = imagePipeline.fetchDecodedImage(imageRequest, null)
|
||||
dataSource.subscribe(object : BaseBitmapDataSubscriber() {
|
||||
override fun onNewResultImpl(bitmap: Bitmap?) {
|
||||
if (bitmap != null && resources != null) {
|
||||
val roundedBitmapDrawable = RoundedBitmapDrawableFactory.create(resources!!, bitmap)
|
||||
roundedBitmapDrawable.isCircular = true
|
||||
roundedBitmapDrawable.setAntiAlias(true)
|
||||
menuItem.icon = roundedBitmapDrawable
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>) {
|
||||
menuItem.setIcon(R.drawable.ic_settings_white_24dp)
|
||||
}
|
||||
}, UiThreadImmediateExecutorService.getInstance())
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLayoutId(): Int {
|
||||
return R.layout.controller_conversations_rv
|
||||
}
|
||||
|
||||
@OnClick(R.id.floatingActionButton, R.id.emptyLayout)
|
||||
fun onFloatingActionButtonClick() {
|
||||
val bundle = Bundle()
|
||||
bundle.putBoolean(BundleKeys.KEY_NEW_CONVERSATION, true)
|
||||
router.pushController(
|
||||
RouterTransaction.with(ContactsController(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
|
||||
override fun getTitle(): String? {
|
||||
return resources!!.getString(R.string.nc_app_name)
|
||||
}
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
view.recyclerView.initRecyclerView(
|
||||
SmoothScrollLinearLayoutManager(view.context), recyclerViewAdapter
|
||||
)
|
||||
|
||||
recyclerViewAdapter.setFastScroller(view.fast_scroller)
|
||||
recyclerViewAdapter.mItemClickListener = this
|
||||
recyclerViewAdapter.mItemLongClickListener = this
|
||||
|
||||
view.fast_scroller.setBubbleTextCreator { position ->
|
||||
var displayName =
|
||||
(recyclerViewAdapter.getItem(position) as ConversationItem).model.displayName
|
||||
|
||||
if (displayName.length > 8) {
|
||||
displayName = displayName.substring(0, 4) + "..."
|
||||
}
|
||||
|
||||
displayName
|
||||
}
|
||||
|
||||
viewModel.loadConversations()
|
||||
}
|
||||
|
||||
override fun onItemLongClick(position: Int) {
|
||||
val clickedItem = recyclerViewAdapter.getItem(position)
|
||||
if (clickedItem != null) {
|
||||
val conversation = (clickedItem as ConversationItem).model
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(BundleKeys.KEY_ROOM, Parcels.wrap<Conversation>(conversation))
|
||||
bundle.putParcelable(BundleKeys.KEY_MENU_TYPE, Parcels.wrap<MenuType>(REGULAR))
|
||||
//prepareAndShowBottomSheetWithBundle(bundle, true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onItemClick(
|
||||
view: View?,
|
||||
position: Int
|
||||
): Boolean {
|
||||
val clickedItem = recyclerViewAdapter.getItem(position)
|
||||
if (clickedItem != null) {
|
||||
val conversation = (clickedItem as ConversationItem).model
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putParcelable(BundleKeys.KEY_USER_ENTITY, viewModel.currentUser)
|
||||
bundle.putString(BundleKeys.KEY_ROOM_TOKEN, conversation.token)
|
||||
bundle.putString(BundleKeys.KEY_ROOM_ID, conversation.roomId)
|
||||
bundle.putParcelable(BundleKeys.KEY_ACTIVE_CONVERSATION, Parcels.wrap(conversation))
|
||||
ConductorRemapping.remapChatController(
|
||||
router, viewModel.currentUser.getId(), conversation.token,
|
||||
bundle, false
|
||||
)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.newarch.features.conversationsList
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
|
||||
import com.nextcloud.talk.newarch.data.model.ErrorModel
|
||||
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
|
||||
import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.FAILED
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADED
|
||||
import com.nextcloud.talk.newarch.mvvm.ViewState.LOADING
|
||||
import com.nextcloud.talk.utils.database.user.UserUtils
|
||||
import org.apache.commons.lang3.builder.CompareToBuilder
|
||||
|
||||
class ConversationsListViewModel constructor(
|
||||
private val conversationsUseCase: GetConversationsUseCase,
|
||||
private val userUtils: UserUtils
|
||||
) : BaseViewModel<ConversationsListView>() {
|
||||
|
||||
val conversationsListData = MutableLiveData<List<Conversation>>()
|
||||
val viewState = MutableLiveData<ViewState>(LOADING)
|
||||
val messageData = MutableLiveData<String>()
|
||||
val searchQuery = MutableLiveData<String>()
|
||||
lateinit var currentUser: UserEntity
|
||||
|
||||
fun loadConversations() {
|
||||
currentUser = userUtils.currentUser
|
||||
|
||||
if (!conversationsUseCase.isUserInitialized() || conversationsUseCase.user != currentUser) {
|
||||
conversationsUseCase.user = currentUser
|
||||
viewState.value = LOADING
|
||||
}
|
||||
|
||||
conversationsUseCase.invoke(
|
||||
backgroundAndUIScope, null, object : UseCaseResponse<List<Conversation>> {
|
||||
override fun onSuccess(result: List<Conversation>) {
|
||||
val newConversations = result.toMutableList()
|
||||
|
||||
newConversations.sortWith(Comparator { conversation1, conversation2 ->
|
||||
CompareToBuilder()
|
||||
.append(conversation2.isFavorite, conversation1.isFavorite)
|
||||
.append(conversation2.lastActivity, conversation1.lastActivity)
|
||||
.toComparison()
|
||||
})
|
||||
|
||||
conversationsListData.value = newConversations
|
||||
viewState.value = LOADED
|
||||
}
|
||||
|
||||
override fun onError(errorModel: ErrorModel?) {
|
||||
messageData.value = errorModel?.message
|
||||
viewState.value = FAILED
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.newarch.features.conversationsList.di.module
|
||||
|
||||
import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
|
||||
import com.nextcloud.talk.newarch.di.module.createApiErrorHandler
|
||||
import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository
|
||||
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
|
||||
import com.nextcloud.talk.newarch.features.conversationsList.ConversationListViewModelFactory
|
||||
import com.nextcloud.talk.utils.database.user.UserUtils
|
||||
import org.koin.dsl.module
|
||||
|
||||
val ConversationsListModule = module {
|
||||
single { createGetConversationsUseCase(get(), createApiErrorHandler()) }
|
||||
//viewModel { ConversationsListViewModel(get(), get()) }
|
||||
factory { createConversationListViewModelFactory(get(), get()) }
|
||||
}
|
||||
|
||||
fun createGetConversationsUseCase(
|
||||
nextcloudTalkRepository: NextcloudTalkRepository,
|
||||
apiErrorHandler: ApiErrorHandler
|
||||
): GetConversationsUseCase {
|
||||
return GetConversationsUseCase(nextcloudTalkRepository, apiErrorHandler)
|
||||
}
|
||||
|
||||
fun createConversationListViewModelFactory(conversationsUseCase: GetConversationsUseCase,
|
||||
userUtils: UserUtils): ConversationListViewModelFactory {
|
||||
return ConversationListViewModelFactory(conversationsUseCase, userUtils)
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
* Heavily inspired by https://android.jlelse.eu/implementing-search-on-type-in-android-with-coroutines-ab117c8f13a4
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.newarch.features.search
|
||||
|
||||
import android.widget.SearchView
|
||||
import androidx.appcompat.widget.SearchView.OnQueryTextListener
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.coroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class DebouncingQueryTextListener(
|
||||
lifecycle: Lifecycle,
|
||||
private val onDebouncingQueryTextChange: (String?) -> Unit
|
||||
) : OnQueryTextListener {
|
||||
var debouncePeriod: Long = 500
|
||||
|
||||
private val coroutineScope = lifecycle.coroutineScope
|
||||
|
||||
private var searchJob: Job? = null
|
||||
|
||||
override fun onQueryTextSubmit(query: String?): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onQueryTextChange(newText: String?): Boolean {
|
||||
searchJob?.cancel()
|
||||
searchJob = coroutineScope.launch {
|
||||
newText?.let {
|
||||
delay(debouncePeriod)
|
||||
onDebouncingQueryTextChange(newText)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.newarch.conversationsList.mvp
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import butterknife.ButterKnife
|
||||
import com.bluelinelabs.conductor.archlifecycle.ControllerLifecycleOwner
|
||||
import com.nextcloud.talk.controllers.base.BaseController
|
||||
import androidx.lifecycle.ViewModelStore
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
|
||||
abstract class BaseView : BaseController(), LifecycleOwner, ViewModelStoreOwner {
|
||||
|
||||
private val viewModelStore = ViewModelStore()
|
||||
private val lifecycleOwner = ControllerLifecycleOwner(this)
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
val view = inflater.inflate(getLayoutId(), container, false)
|
||||
unbinder = ButterKnife.bind(this, view)
|
||||
onViewBound(view)
|
||||
return view
|
||||
}
|
||||
|
||||
override fun inflateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup
|
||||
): View {
|
||||
return inflateView(inflater, container)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
viewModelStore.clear();
|
||||
}
|
||||
|
||||
override fun getLifecycle(): Lifecycle {
|
||||
return lifecycleOwner.lifecycle
|
||||
}
|
||||
|
||||
fun viewModelProvider(): ViewModelProvider {
|
||||
return viewModelProvider(ViewModelProvider.AndroidViewModelFactory(activity!!.application))
|
||||
}
|
||||
|
||||
fun viewModelProvider(factory: ViewModelProvider.NewInstanceFactory): ViewModelProvider {
|
||||
return ViewModelProvider(viewModelStore, factory)
|
||||
}
|
||||
|
||||
fun viewModelProvider(factory: ViewModelProvider.Factory): ViewModelProvider {
|
||||
return ViewModelProvider(viewModelStore, factory)
|
||||
}
|
||||
|
||||
override fun getViewModelStore(): ViewModelStore {
|
||||
return viewModelStore
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
protected abstract fun getLayoutId(): Int
|
||||
}
|
@ -20,23 +20,29 @@
|
||||
|
||||
package com.nextcloud.talk.newarch.conversationsList.mvp
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
|
||||
abstract class BasePresenter<V : BaseView> : MvpPresenter {
|
||||
abstract class BaseViewModel<V : BaseView> : ViewModel() {
|
||||
|
||||
protected val disposables: CompositeDisposable = CompositeDisposable()
|
||||
protected var view: V? = null
|
||||
private set
|
||||
protected val disposables: CompositeDisposable = CompositeDisposable()
|
||||
|
||||
fun start(view: V) {
|
||||
this.view = view
|
||||
}
|
||||
val backgroundAndUIScope = CoroutineScope(
|
||||
Job() + Dispatchers.Main
|
||||
)
|
||||
|
||||
override fun stop() {
|
||||
this.view = null
|
||||
}
|
||||
val backgroundScope = CoroutineScope(
|
||||
Job()
|
||||
)
|
||||
|
||||
override fun destroy() {
|
||||
disposables.clear()
|
||||
}
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
disposables.clear()
|
||||
backgroundAndUIScope.cancel()
|
||||
backgroundScope.cancel()
|
||||
}
|
||||
}
|
@ -18,9 +18,10 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.newarch.conversationsList.mvp
|
||||
package com.nextcloud.talk.newarch.mvvm
|
||||
|
||||
interface MvpPresenter {
|
||||
fun stop()
|
||||
fun destroy()
|
||||
enum class ViewState {
|
||||
LOADING,
|
||||
LOADED,
|
||||
FAILED
|
||||
}
|
@ -18,17 +18,16 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.newarch.conversationsList
|
||||
package com.nextcloud.talk.newarch.mvvm.ext
|
||||
|
||||
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class ConversationsListContract {
|
||||
interface View {
|
||||
fun onLoadConversationsSuccess(conversations: List<Conversation>)
|
||||
fun onLoadConversationsFailure(throwable: Throwable)
|
||||
}
|
||||
|
||||
interface Presenter {
|
||||
fun loadConversations()
|
||||
}
|
||||
}
|
||||
fun RecyclerView.initRecyclerView(
|
||||
layoutManager: RecyclerView.LayoutManager,
|
||||
adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>,
|
||||
hasFixedSize: Boolean = true
|
||||
) {
|
||||
this.layoutManager = layoutManager
|
||||
this.adapter = adapter
|
||||
setHasFixedSize(hasFixedSize)
|
||||
}
|
@ -27,31 +27,41 @@ import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.nextcloud.talk.controllers.ChatController
|
||||
|
||||
object ConductorRemapping {
|
||||
fun remapChatController(router: Router, internalUserId: Long, roomTokenOrId: String, bundle: Bundle, replaceTop: Boolean) {
|
||||
val tag = "$internalUserId@$roomTokenOrId"
|
||||
if (router.getControllerWithTag(tag) != null) {
|
||||
val backstack = router.backstack
|
||||
var routerTransaction: RouterTransaction? = null
|
||||
for (i in 0 until router.backstackSize) {
|
||||
if (tag == backstack[i].tag()) {
|
||||
routerTransaction = backstack[i]
|
||||
backstack.remove(routerTransaction)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
backstack.add(routerTransaction)
|
||||
router.setBackstack(backstack, HorizontalChangeHandler())
|
||||
} else {
|
||||
if (!replaceTop) {
|
||||
router.pushController(RouterTransaction.with(ChatController(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()).tag(tag))
|
||||
} else {
|
||||
router.replaceTopController(RouterTransaction.with(ChatController(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()).tag(tag))
|
||||
}
|
||||
fun remapChatController(
|
||||
router: Router,
|
||||
internalUserId: Long,
|
||||
roomTokenOrId: String,
|
||||
bundle: Bundle,
|
||||
replaceTop: Boolean
|
||||
) {
|
||||
val tag = "$internalUserId@$roomTokenOrId"
|
||||
if (router.getControllerWithTag(tag) != null) {
|
||||
val backstack = router.backstack
|
||||
var routerTransaction: RouterTransaction? = null
|
||||
for (i in 0 until router.backstackSize) {
|
||||
if (tag == backstack[i].tag()) {
|
||||
routerTransaction = backstack[i]
|
||||
backstack.remove(routerTransaction)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
backstack.add(routerTransaction)
|
||||
router.setBackstack(backstack, HorizontalChangeHandler())
|
||||
} else {
|
||||
if (!replaceTop) {
|
||||
router.pushController(
|
||||
RouterTransaction.with(ChatController(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()).tag(tag)
|
||||
)
|
||||
} else {
|
||||
router.replaceTopController(
|
||||
RouterTransaction.with(ChatController(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()).tag(tag)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ public class PushUtils {
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.d(TAG, "No such algorithm");
|
||||
} catch (IOException e) {
|
||||
Log.d(TAG, "Error while trying to parse push configuration state");
|
||||
Log.d(TAG, "Error while trying to parse push configuration viewState");
|
||||
} catch (InvalidKeyException e) {
|
||||
Log.d(TAG, "Invalid key while trying to verify");
|
||||
} catch (SignatureException e) {
|
||||
|
@ -125,7 +125,7 @@ public class MagicAudioManager {
|
||||
// Tablet devices (e.g. Nexus 7) does not support proximity sensors.
|
||||
// Note that, the sensor will not be active until start() has been called.
|
||||
proximitySensor = MagicProximitySensor.create(context, new Runnable() {
|
||||
// This method will be called each time a state change is detected.
|
||||
// This method will be called each time a viewState change is detected.
|
||||
// Example: user holds his hand over the device (closer than ~5 cm),
|
||||
// or removes his hand from the device.
|
||||
public void run() {
|
||||
@ -160,7 +160,7 @@ public class MagicAudioManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when the proximity sensor reports a state change,
|
||||
* This method is called when the proximity sensor reports a viewState change,
|
||||
* e.g. from "NEAR to FAR" or from "FAR to NEAR".
|
||||
*/
|
||||
private void onProximitySensorChangedState() {
|
||||
@ -205,7 +205,7 @@ public class MagicAudioManager {
|
||||
this.audioManagerEvents = audioManagerEvents;
|
||||
amState = AudioManagerState.RUNNING;
|
||||
|
||||
// Store current audio state so we can restore it when stop() is called.
|
||||
// Store current audio viewState so we can restore it when stop() is called.
|
||||
savedAudioMode = audioManager.getMode();
|
||||
savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
|
||||
savedIsMicrophoneMute = audioManager.isMicrophoneMute();
|
||||
@ -294,7 +294,7 @@ public class MagicAudioManager {
|
||||
Log.d(TAG, "stop");
|
||||
ThreadUtils.checkIsOnMainThread();
|
||||
if (amState != AudioManagerState.RUNNING) {
|
||||
Log.e(TAG, "Trying to stop AudioManager in incorrect state: " + amState);
|
||||
Log.e(TAG, "Trying to stop AudioManager in incorrect viewState: " + amState);
|
||||
return;
|
||||
}
|
||||
amState = AudioManagerState.UNINITIALIZED;
|
||||
@ -434,7 +434,7 @@ public class MagicAudioManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the microphone mute state.
|
||||
* Sets the microphone mute viewState.
|
||||
*/
|
||||
private void setMicrophoneMute(boolean on) {
|
||||
boolean wasMuted = audioManager.isMicrophoneMute();
|
||||
@ -445,7 +445,7 @@ public class MagicAudioManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current earpiece state.
|
||||
* Gets the current earpiece viewState.
|
||||
*/
|
||||
private boolean hasEarpiece() {
|
||||
return magicContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
|
||||
@ -480,21 +480,21 @@ public class MagicAudioManager {
|
||||
|
||||
/**
|
||||
* Updates list of possible audio devices and make new device selection.
|
||||
* TODO(henrika): add unit test to verify all state transitions.
|
||||
* TODO(henrika): add unit test to verify all viewState transitions.
|
||||
*/
|
||||
public void updateAudioDeviceState() {
|
||||
ThreadUtils.checkIsOnMainThread();
|
||||
Log.d(TAG, "--- updateAudioDeviceState: "
|
||||
+ "wired headset=" + hasWiredHeadset + ", "
|
||||
+ "BT state=" + bluetoothManager.getState());
|
||||
+ "BT viewState=" + bluetoothManager.getState());
|
||||
Log.d(TAG, "Device status: "
|
||||
+ "available=" + audioDevices + ", "
|
||||
+ "selected=" + selectedAudioDevice + ", "
|
||||
+ "user selected=" + userSelectedAudioDevice);
|
||||
|
||||
// Check if any Bluetooth headset is connected. The internal BT state will
|
||||
// Check if any Bluetooth headset is connected. The internal BT viewState will
|
||||
// change accordingly.
|
||||
// TODO(henrika): perhaps wrap required state into BT manager.
|
||||
// TODO(henrika): perhaps wrap required viewState into BT manager.
|
||||
if (bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_AVAILABLE
|
||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE
|
||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_DISCONNECTING) {
|
||||
@ -521,7 +521,7 @@ public class MagicAudioManager {
|
||||
newAudioDevices.add(AudioDevice.EARPIECE);
|
||||
}
|
||||
}
|
||||
// Store state which is set to true if the device list has changed.
|
||||
// Store viewState which is set to true if the device list has changed.
|
||||
boolean audioDeviceSetUpdated = !audioDevices.equals(newAudioDevices);
|
||||
// Update the existing audio device set.
|
||||
audioDevices = newAudioDevices;
|
||||
@ -562,7 +562,7 @@ public class MagicAudioManager {
|
||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED) {
|
||||
Log.d(TAG, "Need BT audio: start=" + needBluetoothAudioStart + ", "
|
||||
+ "stop=" + needBluetoothAudioStop + ", "
|
||||
+ "BT state=" + bluetoothManager.getState());
|
||||
+ "BT viewState=" + bluetoothManager.getState());
|
||||
}
|
||||
|
||||
// Start or stop Bluetooth SCO connection given states set earlier.
|
||||
@ -624,7 +624,7 @@ public class MagicAudioManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* AudioManager state.
|
||||
* AudioManager viewState.
|
||||
*/
|
||||
public enum AudioManagerState {
|
||||
UNINITIALIZED,
|
||||
@ -649,7 +649,7 @@ public class MagicAudioManager {
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
int state = intent.getIntExtra("state", STATE_UNPLUGGED);
|
||||
int state = intent.getIntExtra("viewState", STATE_UNPLUGGED);
|
||||
// int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
|
||||
// String name = intent.getStringExtra("name");
|
||||
hasWiredHeadset = (state == STATE_PLUGGED);
|
||||
|
@ -98,7 +98,7 @@ public class MagicBluetoothManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the internal state.
|
||||
* Returns the internal viewState.
|
||||
*/
|
||||
public State getState() {
|
||||
ThreadUtils.checkIsOnMainThread();
|
||||
@ -110,14 +110,14 @@ public class MagicBluetoothManager {
|
||||
/**
|
||||
* Activates components required to detect Bluetooth devices and to enable
|
||||
* BT SCO (audio is routed via BT SCO) for the headset profile. The end
|
||||
* state will be HEADSET_UNAVAILABLE but a state machine has started which
|
||||
* will start a state change sequence where the final outcome depends on
|
||||
* viewState will be HEADSET_UNAVAILABLE but a viewState machine has started which
|
||||
* will start a viewState change sequence where the final outcome depends on
|
||||
* if/when the BT headset is enabled.
|
||||
* Example of state change sequence when start() is called while BT device
|
||||
* Example of viewState change sequence when start() is called while BT device
|
||||
* is connected and enabled:
|
||||
* UNINITIALIZED --> HEADSET_UNAVAILABLE --> HEADSET_AVAILABLE -->
|
||||
* SCO_CONNECTING --> SCO_CONNECTED <==> audio is now routed via BT SCO.
|
||||
* Note that the MagicAudioManager is also involved in driving this state
|
||||
* Note that the MagicAudioManager is also involved in driving this viewState
|
||||
* change.
|
||||
*/
|
||||
public void start() {
|
||||
@ -128,7 +128,7 @@ public class MagicBluetoothManager {
|
||||
return;
|
||||
}
|
||||
if (bluetoothState != State.UNINITIALIZED) {
|
||||
Log.w(TAG, "Invalid BT state");
|
||||
Log.w(TAG, "Invalid BT viewState");
|
||||
return;
|
||||
}
|
||||
bluetoothHeadset = null;
|
||||
@ -155,16 +155,16 @@ public class MagicBluetoothManager {
|
||||
}
|
||||
// Register receivers for BluetoothHeadset change notifications.
|
||||
IntentFilter bluetoothHeadsetFilter = new IntentFilter();
|
||||
// Register receiver for change in connection state of the Headset profile.
|
||||
// Register receiver for change in connection viewState of the Headset profile.
|
||||
bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
|
||||
// Register receiver for change in audio connection state of the Headset profile.
|
||||
// Register receiver for change in audio connection viewState of the Headset profile.
|
||||
bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
|
||||
registerReceiver(bluetoothHeadsetReceiver, bluetoothHeadsetFilter);
|
||||
Log.d(TAG, "HEADSET profile state: "
|
||||
Log.d(TAG, "HEADSET profile viewState: "
|
||||
+ stateToString(bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET)));
|
||||
Log.d(TAG, "Bluetooth proxy for headset profile has started");
|
||||
bluetoothState = State.HEADSET_UNAVAILABLE;
|
||||
Log.d(TAG, "start done: BT state=" + bluetoothState);
|
||||
Log.d(TAG, "start done: BT viewState=" + bluetoothState);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,7 +172,7 @@ public class MagicBluetoothManager {
|
||||
*/
|
||||
public void stop() {
|
||||
ThreadUtils.checkIsOnMainThread();
|
||||
Log.d(TAG, "stop: BT state=" + bluetoothState);
|
||||
Log.d(TAG, "stop: BT viewState=" + bluetoothState);
|
||||
if (bluetoothAdapter == null) {
|
||||
return;
|
||||
}
|
||||
@ -191,7 +191,7 @@ public class MagicBluetoothManager {
|
||||
bluetoothAdapter = null;
|
||||
bluetoothDevice = null;
|
||||
bluetoothState = State.UNINITIALIZED;
|
||||
Log.d(TAG, "stop done: BT state=" + bluetoothState);
|
||||
Log.d(TAG, "stop done: BT viewState=" + bluetoothState);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -209,7 +209,7 @@ public class MagicBluetoothManager {
|
||||
*/
|
||||
public boolean startScoAudio() {
|
||||
ThreadUtils.checkIsOnMainThread();
|
||||
Log.d(TAG, "startSco: BT state=" + bluetoothState + ", "
|
||||
Log.d(TAG, "startSco: BT viewState=" + bluetoothState + ", "
|
||||
+ "attempts: " + scoConnectionAttempts + ", "
|
||||
+ "SCO is on: " + isScoOn());
|
||||
if (scoConnectionAttempts >= MAX_SCO_CONNECTION_ATTEMPTS) {
|
||||
@ -224,13 +224,13 @@ public class MagicBluetoothManager {
|
||||
Log.d(TAG, "Starting Bluetooth SCO and waits for ACTION_AUDIO_STATE_CHANGED...");
|
||||
// The SCO connection establishment can take several seconds, hence we cannot rely on the
|
||||
// connection to be available when the method returns but instead register to receive the
|
||||
// intent ACTION_SCO_AUDIO_STATE_UPDATED and wait for the state to be SCO_AUDIO_STATE_CONNECTED.
|
||||
// intent ACTION_SCO_AUDIO_STATE_UPDATED and wait for the viewState to be SCO_AUDIO_STATE_CONNECTED.
|
||||
bluetoothState = State.SCO_CONNECTING;
|
||||
audioManager.startBluetoothSco();
|
||||
audioManager.setBluetoothScoOn(true);
|
||||
scoConnectionAttempts++;
|
||||
startTimer();
|
||||
Log.d(TAG, "startScoAudio done: BT state=" + bluetoothState + ", "
|
||||
Log.d(TAG, "startScoAudio done: BT viewState=" + bluetoothState + ", "
|
||||
+ "SCO is on: " + isScoOn());
|
||||
return true;
|
||||
}
|
||||
@ -240,7 +240,7 @@ public class MagicBluetoothManager {
|
||||
*/
|
||||
public void stopScoAudio() {
|
||||
ThreadUtils.checkIsOnMainThread();
|
||||
Log.d(TAG, "stopScoAudio: BT state=" + bluetoothState + ", "
|
||||
Log.d(TAG, "stopScoAudio: BT viewState=" + bluetoothState + ", "
|
||||
+ "SCO is on: " + isScoOn());
|
||||
if (bluetoothState != State.SCO_CONNECTING && bluetoothState != State.SCO_CONNECTED) {
|
||||
return;
|
||||
@ -249,14 +249,14 @@ public class MagicBluetoothManager {
|
||||
audioManager.stopBluetoothSco();
|
||||
audioManager.setBluetoothScoOn(false);
|
||||
bluetoothState = State.SCO_DISCONNECTING;
|
||||
Log.d(TAG, "stopScoAudio done: BT state=" + bluetoothState + ", "
|
||||
Log.d(TAG, "stopScoAudio done: BT viewState=" + bluetoothState + ", "
|
||||
+ "SCO is on: " + isScoOn());
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the BluetoothHeadset proxy object (controls the Bluetooth Headset
|
||||
* Service via IPC) to update the list of connected devices for the HEADSET
|
||||
* profile. The internal state will change to HEADSET_UNAVAILABLE or to
|
||||
* profile. The internal viewState will change to HEADSET_UNAVAILABLE or to
|
||||
* HEADSET_AVAILABLE and |bluetoothDevice| will be mapped to the connected
|
||||
* device if available.
|
||||
*/
|
||||
@ -266,7 +266,7 @@ public class MagicBluetoothManager {
|
||||
}
|
||||
Log.d(TAG, "updateDevice");
|
||||
// Get connected devices for the headset profile. Returns the set of
|
||||
// devices which are in state STATE_CONNECTED. The BluetoothDevice class
|
||||
// devices which are in viewState STATE_CONNECTED. The BluetoothDevice class
|
||||
// is just a thin wrapper for a Bluetooth hardware address.
|
||||
List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices();
|
||||
if (devices.isEmpty()) {
|
||||
@ -279,10 +279,10 @@ public class MagicBluetoothManager {
|
||||
bluetoothState = State.HEADSET_AVAILABLE;
|
||||
Log.d(TAG, "Connected bluetooth headset: "
|
||||
+ "name=" + bluetoothDevice.getName() + ", "
|
||||
+ "state=" + stateToString(bluetoothHeadset.getConnectionState(bluetoothDevice))
|
||||
+ "viewState=" + stateToString(bluetoothHeadset.getConnectionState(bluetoothDevice))
|
||||
+ ", SCO audio=" + bluetoothHeadset.isAudioConnected(bluetoothDevice));
|
||||
}
|
||||
Log.d(TAG, "updateDevice done: BT state=" + bluetoothState);
|
||||
Log.d(TAG, "updateDevice done: BT viewState=" + bluetoothState);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -311,13 +311,13 @@ public class MagicBluetoothManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the state of the local Bluetooth adapter.
|
||||
* Logs the viewState of the local Bluetooth adapter.
|
||||
*/
|
||||
@SuppressLint("HardwareIds")
|
||||
protected void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) {
|
||||
Log.d(TAG, "BluetoothAdapter: "
|
||||
+ "enabled=" + localAdapter.isEnabled() + ", "
|
||||
+ "state=" + stateToString(localAdapter.getState()) + ", "
|
||||
+ "viewState=" + stateToString(localAdapter.getState()) + ", "
|
||||
+ "name=" + localAdapter.getName() + ", "
|
||||
+ "address=" + localAdapter.getAddress());
|
||||
// Log the set of BluetoothDevice objects that are bonded (paired) to the local adapter.
|
||||
@ -366,7 +366,7 @@ public class MagicBluetoothManager {
|
||||
if (bluetoothState == State.UNINITIALIZED || bluetoothHeadset == null) {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "bluetoothTimeout: BT state=" + bluetoothState + ", "
|
||||
Log.d(TAG, "bluetoothTimeout: BT viewState=" + bluetoothState + ", "
|
||||
+ "attempts: " + scoConnectionAttempts + ", "
|
||||
+ "SCO is on: " + isScoOn());
|
||||
if (bluetoothState != State.SCO_CONNECTING) {
|
||||
@ -385,7 +385,7 @@ public class MagicBluetoothManager {
|
||||
}
|
||||
}
|
||||
if (scoConnected) {
|
||||
// We thought BT had timed out, but it's actually on; updating state.
|
||||
// We thought BT had timed out, but it's actually on; updating viewState.
|
||||
bluetoothState = State.SCO_CONNECTED;
|
||||
scoConnectionAttempts = 0;
|
||||
} else {
|
||||
@ -394,7 +394,7 @@ public class MagicBluetoothManager {
|
||||
stopScoAudio();
|
||||
}
|
||||
updateAudioDeviceState();
|
||||
Log.d(TAG, "bluetoothTimeout done: BT state=" + bluetoothState);
|
||||
Log.d(TAG, "bluetoothTimeout done: BT viewState=" + bluetoothState);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -434,7 +434,7 @@ public class MagicBluetoothManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Bluetooth connection state.
|
||||
// Bluetooth connection viewState.
|
||||
public enum State {
|
||||
// Bluetooth is not available; no adapter or Bluetooth is off.
|
||||
UNINITIALIZED,
|
||||
@ -461,17 +461,17 @@ public class MagicBluetoothManager {
|
||||
private class BluetoothServiceListener implements BluetoothProfile.ServiceListener {
|
||||
@Override
|
||||
// Called to notify the client when the proxy object has been connected to the service.
|
||||
// Once we have the profile proxy object, we can use it to monitor the state of the
|
||||
// Once we have the profile proxy object, we can use it to monitor the viewState of the
|
||||
// connection and perform other operations that are relevant to the headset profile.
|
||||
public void onServiceConnected(int profile, BluetoothProfile proxy) {
|
||||
if (profile != BluetoothProfile.HEADSET || bluetoothState == State.UNINITIALIZED) {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "BluetoothServiceListener.onServiceConnected: BT state=" + bluetoothState);
|
||||
Log.d(TAG, "BluetoothServiceListener.onServiceConnected: BT viewState=" + bluetoothState);
|
||||
// Android only supports one connected Bluetooth Headset at a time.
|
||||
bluetoothHeadset = (BluetoothHeadset) proxy;
|
||||
updateAudioDeviceState();
|
||||
Log.d(TAG, "onServiceConnected done: BT state=" + bluetoothState);
|
||||
Log.d(TAG, "onServiceConnected done: BT viewState=" + bluetoothState);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -480,18 +480,18 @@ public class MagicBluetoothManager {
|
||||
if (profile != BluetoothProfile.HEADSET || bluetoothState == State.UNINITIALIZED) {
|
||||
return;
|
||||
}
|
||||
Log.d(TAG, "BluetoothServiceListener.onServiceDisconnected: BT state=" + bluetoothState);
|
||||
Log.d(TAG, "BluetoothServiceListener.onServiceDisconnected: BT viewState=" + bluetoothState);
|
||||
stopScoAudio();
|
||||
bluetoothHeadset = null;
|
||||
bluetoothDevice = null;
|
||||
bluetoothState = State.HEADSET_UNAVAILABLE;
|
||||
updateAudioDeviceState();
|
||||
Log.d(TAG, "onServiceDisconnected done: BT state=" + bluetoothState);
|
||||
Log.d(TAG, "onServiceDisconnected done: BT viewState=" + bluetoothState);
|
||||
}
|
||||
}
|
||||
|
||||
// Intent broadcast receiver which handles changes in Bluetooth device availability.
|
||||
// Detects headset changes and Bluetooth SCO state changes.
|
||||
// Detects headset changes and Bluetooth SCO viewState changes.
|
||||
private class BluetoothHeadsetBroadcastReceiver extends BroadcastReceiver {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
@ -499,7 +499,7 @@ public class MagicBluetoothManager {
|
||||
return;
|
||||
}
|
||||
final String action = intent.getAction();
|
||||
// Change in connection state of the Headset profile. Note that the
|
||||
// Change in connection viewState of the Headset profile. Note that the
|
||||
// change does not tell us anything about whether we're streaming
|
||||
// audio to BT over SCO. Typically received when user turns on a BT
|
||||
// headset while audio is active using another audio device.
|
||||
@ -510,7 +510,7 @@ public class MagicBluetoothManager {
|
||||
+ "a=ACTION_CONNECTION_STATE_CHANGED, "
|
||||
+ "s=" + stateToString(state) + ", "
|
||||
+ "sb=" + isInitialStickyBroadcast() + ", "
|
||||
+ "BT state: " + bluetoothState);
|
||||
+ "BT viewState: " + bluetoothState);
|
||||
if (state == BluetoothHeadset.STATE_CONNECTED) {
|
||||
scoConnectionAttempts = 0;
|
||||
updateAudioDeviceState();
|
||||
@ -523,7 +523,7 @@ public class MagicBluetoothManager {
|
||||
stopScoAudio();
|
||||
updateAudioDeviceState();
|
||||
}
|
||||
// Change in the audio (SCO) connection state of the Headset profile.
|
||||
// Change in the audio (SCO) connection viewState of the Headset profile.
|
||||
// Typically received after call to startScoAudio() has finalized.
|
||||
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
|
||||
final int state = intent.getIntExtra(
|
||||
@ -532,7 +532,7 @@ public class MagicBluetoothManager {
|
||||
+ "a=ACTION_AUDIO_STATE_CHANGED, "
|
||||
+ "s=" + stateToString(state) + ", "
|
||||
+ "sb=" + isInitialStickyBroadcast() + ", "
|
||||
+ "BT state: " + bluetoothState);
|
||||
+ "BT viewState: " + bluetoothState);
|
||||
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
|
||||
cancelTimer();
|
||||
if (bluetoothState == State.SCO_CONNECTING) {
|
||||
@ -541,7 +541,7 @@ public class MagicBluetoothManager {
|
||||
scoConnectionAttempts = 0;
|
||||
updateAudioDeviceState();
|
||||
} else {
|
||||
Log.w(TAG, "Unexpected state BluetoothHeadset.STATE_AUDIO_CONNECTED");
|
||||
Log.w(TAG, "Unexpected viewState BluetoothHeadset.STATE_AUDIO_CONNECTED");
|
||||
}
|
||||
} else if (state == BluetoothHeadset.STATE_AUDIO_CONNECTING) {
|
||||
Log.d(TAG, "+++ Bluetooth audio SCO is now connecting...");
|
||||
@ -554,7 +554,7 @@ public class MagicBluetoothManager {
|
||||
updateAudioDeviceState();
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "onReceive done: BT state=" + bluetoothState);
|
||||
Log.d(TAG, "onReceive done: BT viewState=" + bluetoothState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ public class MagicProximitySensor implements SensorEventListener {
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for last reported state. Set to true if "near" is reported.
|
||||
* Getter for last reported viewState. Set to true if "near" is reported.
|
||||
*/
|
||||
boolean sensorReportsNearState() {
|
||||
threadChecker.checkIsOnValidThread();
|
||||
@ -124,15 +124,15 @@ public class MagicProximitySensor implements SensorEventListener {
|
||||
// avoid blocking.
|
||||
float distanceInCentimeters = event.values[0];
|
||||
if (distanceInCentimeters < proximitySensor.getMaximumRange()) {
|
||||
Log.d(TAG, "Proximity sensor => NEAR state");
|
||||
Log.d(TAG, "Proximity sensor => NEAR viewState");
|
||||
lastStateReportIsNear = true;
|
||||
} else {
|
||||
Log.d(TAG, "Proximity sensor => FAR state");
|
||||
Log.d(TAG, "Proximity sensor => FAR viewState");
|
||||
lastStateReportIsNear = false;
|
||||
}
|
||||
|
||||
// Report about new state to listening client. Client can then call
|
||||
// sensorReportsNearState() to query the current state (NEAR or FAR).
|
||||
// Report about new viewState to listening client. Client can then call
|
||||
// sensorReportsNearState() to query the current viewState (NEAR or FAR).
|
||||
if (onSensorStateListener != null) {
|
||||
onSensorStateListener.run();
|
||||
}
|
||||
|
@ -27,7 +27,7 @@
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_above="@id/bottom_navigation"
|
||||
@ -53,7 +53,7 @@
|
||||
layout="@layout/fast_scroller"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignTop="@id/recycler_view"
|
||||
android:layout_alignBottom="@id/recycler_view" />
|
||||
android:layout_alignTop="@id/recyclerView"
|
||||
android:layout_alignBottom="@id/recyclerView" />
|
||||
|
||||
</RelativeLayout>
|
@ -25,7 +25,7 @@
|
||||
android:background="@color/nc_white_color">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/rv_item_conversation" />
|
||||
|
@ -106,7 +106,7 @@
|
||||
tools:ignore="UnknownIdInLayout">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:listitem="@layout/rv_item_contact" />
|
||||
|
@ -75,7 +75,7 @@
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/rv_item_conversation" />
|
||||
@ -90,6 +90,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:visibility="gone"
|
||||
app:srcCompat="@drawable/ic_add_white_24px" />
|
||||
|
||||
<include layout="@layout/fast_scroller" />
|
||||
|
@ -34,7 +34,7 @@
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:id="@+id/recyclerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:listitem="@layout/rv_item_conversation" />
|
||||
|
@ -27,6 +27,7 @@
|
||||
android:title="@string/nc_search"
|
||||
android:icon="@drawable/ic_search_white_24dp"
|
||||
app:showAsAction="collapseActionView|always"
|
||||
android:visible="false"
|
||||
android:animateLayoutChanges="true"
|
||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||
/>
|
||||
|
@ -22,7 +22,7 @@
|
||||
buildscript {
|
||||
|
||||
ext {
|
||||
kotlinVersion = '1.3.50'
|
||||
kotlin_version = '1.3.50'
|
||||
}
|
||||
|
||||
repositories {
|
||||
@ -34,7 +34,7 @@ buildscript {
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}"
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}"
|
||||
classpath "io.realm:realm-gradle-plugin:6.0.0"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
|
Loading…
Reference in New Issue
Block a user