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 {
|
dexOptions {
|
||||||
@ -136,8 +140,9 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
workVersion = "1.0.1"
|
work_version = "1.0.1"
|
||||||
koin_version = "2.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.koin:koin-androidx-ext:$koin_version"
|
||||||
|
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
|
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 'androidx.appcompat:appcompat:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.1.0-beta01'
|
implementation 'com.google.android.material:material:1.1.0-beta01'
|
||||||
@ -168,10 +192,10 @@ dependencies {
|
|||||||
implementation 'com.github.vanniktech:Emoji:0.6.0'
|
implementation 'com.github.vanniktech:Emoji:0.6.0'
|
||||||
implementation group: 'androidx.emoji', name: 'emoji-bundled', version: '1.0.0'
|
implementation group: 'androidx.emoji', name: 'emoji-bundled', version: '1.0.0'
|
||||||
implementation 'org.michaelevans.colorart:library:0.0.3'
|
implementation 'org.michaelevans.colorart:library:0.0.3'
|
||||||
implementation "android.arch.work:work-runtime:${workVersion}"
|
implementation "android.arch.work:work-runtime:${work_version}"
|
||||||
implementation "android.arch.work:work-rxjava2:${workVersion}"
|
implementation "android.arch.work:work-rxjava2:${work_version}"
|
||||||
implementation 'com.google.android:flexbox:1.1.0'
|
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', {
|
implementation ('com.gitlab.bitfireAT:dav4jvm:f2078bc846', {
|
||||||
exclude group: 'org.ogce', module: 'xpp3' // Android comes with its own XmlPullParser
|
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.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.lifecycle:lifecycle-extensions:2.1.0"
|
||||||
|
|
||||||
implementation 'androidx.multidex:multidex:2.0.1'
|
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-archlifecycle:3.0.0-rc2'
|
||||||
implementation 'com.bluelinelabs:conductor-rxlifecycle2:3.0.0-rc2'
|
implementation 'com.bluelinelabs:conductor-rxlifecycle2:3.0.0-rc2'
|
||||||
implementation 'com.bluelinelabs:conductor-autodispose: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:4.2.2'
|
||||||
implementation 'com.squareup.okhttp3:okhttp-urlconnection: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 'com.github.HITGIF:TextFieldBoxes:1.4.5'
|
||||||
implementation 'eu.davidea:flexible-adapter:5.1.0'
|
implementation 'eu.davidea:flexible-adapter:5.1.0'
|
||||||
implementation 'eu.davidea:flexible-adapter-ui:1.0.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 'org.webrtc:google-webrtc:1.0.23295'
|
||||||
implementation 'com.yarolegovich:lovely-dialog:1.1.0'
|
implementation 'com.yarolegovich:lovely-dialog:1.1.0'
|
||||||
implementation 'com.yarolegovich:lovelyinput:1.0.9'
|
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.LockedController
|
||||||
import com.nextcloud.talk.controllers.ServerSelectionController
|
import com.nextcloud.talk.controllers.ServerSelectionController
|
||||||
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
|
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.ConductorRemapping
|
||||||
import com.nextcloud.talk.utils.SecurityUtils
|
import com.nextcloud.talk.utils.SecurityUtils
|
||||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
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 (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
|
||||||
if (!router!!.hasRootController()) {
|
if (!router!!.hasRootController()) {
|
||||||
router!!.setRoot(RouterTransaction.with(ConversationsListController())
|
router!!.setRoot(RouterTransaction.with(ConversationsListView())
|
||||||
.pushChangeHandler(HorizontalChangeHandler())
|
.pushChangeHandler(HorizontalChangeHandler())
|
||||||
.popChangeHandler(HorizontalChangeHandler()))
|
.popChangeHandler(HorizontalChangeHandler()))
|
||||||
}
|
}
|
||||||
@ -99,7 +100,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
|||||||
} else if (!router!!.hasRootController()) {
|
} else if (!router!!.hasRootController()) {
|
||||||
if (hasDb) {
|
if (hasDb) {
|
||||||
if (userUtils.anyUserExists()) {
|
if (userUtils.anyUserExists()) {
|
||||||
router!!.setRoot(RouterTransaction.with(ConversationsListController())
|
router!!.setRoot(RouterTransaction.with(ConversationsListView())
|
||||||
.pushChangeHandler(HorizontalChangeHandler())
|
.pushChangeHandler(HorizontalChangeHandler())
|
||||||
.popChangeHandler(HorizontalChangeHandler()))
|
.popChangeHandler(HorizontalChangeHandler()))
|
||||||
} else {
|
} else {
|
||||||
|
@ -60,7 +60,7 @@ public class ConversationItem extends AbstractFlexibleItem<ConversationItem.Conv
|
|||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
public ConversationItem(Conversation conversation, UserEntity userEntity,
|
public ConversationItem(Conversation conversation, UserEntity userEntity,
|
||||||
Context activityContext) {
|
Context activityContext) {
|
||||||
this.conversation = conversation;
|
this.conversation = conversation;
|
||||||
this.userEntity = userEntity;
|
this.userEntity = userEntity;
|
||||||
this.context = activityContext;
|
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) {
|
public void bindViewHolder(FlexibleAdapter<IFlexible> adapter, ConversationItemViewHolder holder, int position, List<Object> payloads) {
|
||||||
Context appContext =
|
Context appContext =
|
||||||
NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext();
|
NextcloudTalkApplication.Companion.getSharedApplication().getApplicationContext();
|
||||||
|
|
||||||
holder.dialogAvatar.setController(null);
|
holder.dialogAvatar.setController(null);
|
||||||
|
|
||||||
if (adapter.hasFilter()) {
|
if (adapter.hasFilter()) {
|
||||||
|
@ -319,7 +319,7 @@ public interface NcApi {
|
|||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@PUT
|
@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
|
@FormUrlEncoded
|
||||||
@ -332,7 +332,7 @@ public interface NcApi {
|
|||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@PUT
|
@PUT
|
||||||
Observable<GenericOverall> setLobbyForConversation(@Header("Authorization") String authorization,
|
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);
|
@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.CommunicationModule
|
||||||
import com.nextcloud.talk.newarch.di.module.NetworkModule
|
import com.nextcloud.talk.newarch.di.module.NetworkModule
|
||||||
import com.nextcloud.talk.newarch.di.module.StorageModule
|
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.ClosedInterfaceImpl
|
||||||
import com.nextcloud.talk.utils.DeviceUtils
|
import com.nextcloud.talk.utils.DeviceUtils
|
||||||
import com.nextcloud.talk.utils.DisplayUtils
|
import com.nextcloud.talk.utils.DisplayUtils
|
||||||
@ -193,7 +194,7 @@ class NextcloudTalkApplication : Application(), LifecycleObserver {
|
|||||||
startKoin {
|
startKoin {
|
||||||
androidContext(this@NextcloudTalkApplication)
|
androidContext(this@NextcloudTalkApplication)
|
||||||
androidLogger()
|
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;
|
private final Set<String> selectedPaths;
|
||||||
@Inject
|
@Inject
|
||||||
UserUtils userUtils;
|
UserUtils userUtils;
|
||||||
@BindView(R.id.recycler_view)
|
@BindView(R.id.recyclerView)
|
||||||
RecyclerView recyclerView;
|
RecyclerView recyclerView;
|
||||||
@BindView(R.id.fast_scroller)
|
@BindView(R.id.fast_scroller)
|
||||||
FastScroller fastScroller;
|
FastScroller fastScroller;
|
||||||
|
@ -84,6 +84,9 @@ public class AccountVerificationController extends BaseController {
|
|||||||
@Inject
|
@Inject
|
||||||
AppPreferences appPreferences;
|
AppPreferences appPreferences;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
EventBus eventBus;
|
||||||
|
|
||||||
@BindView(R.id.progress_text)
|
@BindView(R.id.progress_text)
|
||||||
TextView progressText;
|
TextView progressText;
|
||||||
|
|
||||||
@ -115,6 +118,18 @@ public class AccountVerificationController extends BaseController {
|
|||||||
return inflater.inflate(R.layout.controller_account_verification, container, false);
|
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
|
@Override
|
||||||
protected void onViewBound(@NonNull View view) {
|
protected void onViewBound(@NonNull View view) {
|
||||||
super.onViewBound(view);
|
super.onViewBound(view);
|
||||||
|
@ -197,6 +197,8 @@ public class CallController extends BaseController {
|
|||||||
AppPreferences appPreferences;
|
AppPreferences appPreferences;
|
||||||
@Inject
|
@Inject
|
||||||
Cache cache;
|
Cache cache;
|
||||||
|
@Inject
|
||||||
|
EventBus eventBus;
|
||||||
|
|
||||||
private PeerConnectionFactory peerConnectionFactory;
|
private PeerConnectionFactory peerConnectionFactory;
|
||||||
private MediaConstraints audioConstraints;
|
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)
|
@Subscribe(threadMode = ThreadMode.BACKGROUND)
|
||||||
public void onMessageEvent(WebSocketCommunicationEvent webSocketCommunicationEvent) {
|
public void onMessageEvent(WebSocketCommunicationEvent webSocketCommunicationEvent) {
|
||||||
switch (webSocketCommunicationEvent.getType()) {
|
switch (webSocketCommunicationEvent.getType()) {
|
||||||
|
@ -160,6 +160,18 @@ public class CallNotificationController extends BaseController {
|
|||||||
return inflater.inflate(R.layout.controller_call_notification, container, false);
|
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() {
|
private void showAnswerControls() {
|
||||||
callAnswerCameraView.setVisibility(View.VISIBLE);
|
callAnswerCameraView.setVisibility(View.VISIBLE);
|
||||||
callAnswerVoiceOnlyView.setVisibility(View.VISIBLE);
|
callAnswerVoiceOnlyView.setVisibility(View.VISIBLE);
|
||||||
|
@ -654,6 +654,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
|||||||
|
|
||||||
override fun onAttach(view: View) {
|
override fun onAttach(view: View) {
|
||||||
super.onAttach(view)
|
super.onAttach(view)
|
||||||
|
eventBus.register(this)
|
||||||
|
|
||||||
if (conversationUser?.userId != "?" && conversationUser?.hasSpreedFeatureCapability(
|
if (conversationUser?.userId != "?" && conversationUser?.hasSpreedFeatureCapability(
|
||||||
"mention-flag"
|
"mention-flag"
|
||||||
@ -728,7 +729,7 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetach(view: View) {
|
override fun onDetach(view: View) {
|
||||||
super.onDetach(view)
|
eventBus.unregister(this)
|
||||||
ApplicationWideCurrentRoomHolder.getInstance()
|
ApplicationWideCurrentRoomHolder.getInstance()
|
||||||
.clear()
|
.clear()
|
||||||
|
|
||||||
@ -747,6 +748,8 @@ class ChatController(args: Bundle) : BaseController(), MessagesListAdapter
|
|||||||
if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) {
|
if (mentionAutocomplete != null && mentionAutocomplete!!.isPopupShowing) {
|
||||||
mentionAutocomplete?.dismissPopup()
|
mentionAutocomplete?.dismissPopup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.onDetach(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
override fun getTitle(): String? {
|
||||||
|
@ -128,7 +128,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
|
|||||||
AppPreferences appPreferences;
|
AppPreferences appPreferences;
|
||||||
@BindView(R.id.progressBar)
|
@BindView(R.id.progressBar)
|
||||||
ProgressBar progressBar;
|
ProgressBar progressBar;
|
||||||
@BindView(R.id.recycler_view)
|
@BindView(R.id.recyclerView)
|
||||||
RecyclerView recyclerView;
|
RecyclerView recyclerView;
|
||||||
|
|
||||||
@BindView(R.id.swipe_refresh_layout)
|
@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);
|
return inflater.inflate(R.layout.controller_contacts_rv, container, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDetach(@NonNull View view) {
|
||||||
|
eventBus.unregister(this);
|
||||||
|
super.onDetach(view);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onAttach(@NonNull View view) {
|
protected void onAttach(@NonNull View view) {
|
||||||
super.onAttach(view);
|
super.onAttach(view);
|
||||||
|
eventBus.register(this);
|
||||||
|
|
||||||
if (isNewConversationView) {
|
if (isNewConversationView) {
|
||||||
toggleNewCallHeaderVisibility(!isPublicCall);
|
toggleNewCallHeaderVisibility(!isPublicCall);
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
package com.nextcloud.talk.controllers
|
package com.nextcloud.talk.controllers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.res.Configuration
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.LayerDrawable
|
import android.graphics.drawable.LayerDrawable
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@ -85,7 +85,6 @@ import io.reactivex.Observer
|
|||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.Disposable
|
import io.reactivex.disposables.Disposable
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
import org.greenrobot.eventbus.EventBus
|
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
import org.greenrobot.eventbus.ThreadMode
|
import org.greenrobot.eventbus.ThreadMode
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
@ -94,7 +93,6 @@ import javax.inject.Inject
|
|||||||
|
|
||||||
@AutoInjector(NextcloudTalkApplication::class)
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
class ConversationInfoController(args: Bundle) : BaseController(), FlexibleAdapter.OnItemClickListener {
|
class ConversationInfoController(args: Bundle) : BaseController(), FlexibleAdapter.OnItemClickListener {
|
||||||
|
|
||||||
@BindView(R.id.notification_settings)
|
@BindView(R.id.notification_settings)
|
||||||
lateinit var notificationsPreferenceScreen: MaterialPreferenceScreen
|
lateinit var notificationsPreferenceScreen: MaterialPreferenceScreen
|
||||||
@BindView(R.id.progressBar)
|
@BindView(R.id.progressBar)
|
||||||
@ -115,7 +113,7 @@ class ConversationInfoController(args: Bundle) : BaseController(), FlexibleAdapt
|
|||||||
lateinit var conversationDisplayName: EmojiTextView
|
lateinit var conversationDisplayName: EmojiTextView
|
||||||
@BindView(R.id.participants_list_category)
|
@BindView(R.id.participants_list_category)
|
||||||
lateinit var participantsListCategory: MaterialPreferenceCategoryWithRightLink
|
lateinit var participantsListCategory: MaterialPreferenceCategoryWithRightLink
|
||||||
@BindView(R.id.recycler_view)
|
@BindView(R.id.recyclerView)
|
||||||
lateinit var recyclerView: RecyclerView
|
lateinit var recyclerView: RecyclerView
|
||||||
@BindView(R.id.deleteConversationAction)
|
@BindView(R.id.deleteConversationAction)
|
||||||
lateinit var deleteConversationAction: MaterialStandardPreference
|
lateinit var deleteConversationAction: MaterialStandardPreference
|
||||||
@ -179,8 +177,14 @@ class ConversationInfoController(args: Bundle) : BaseController(), FlexibleAdapt
|
|||||||
return inflater.inflate(R.layout.controller_conversation_info, container, false)
|
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) {
|
override fun onAttach(view: View) {
|
||||||
super.onAttach(view)
|
super.onAttach(view)
|
||||||
|
eventBus.register(this)
|
||||||
|
|
||||||
if (databaseStorageModule == null) {
|
if (databaseStorageModule == null) {
|
||||||
databaseStorageModule = DatabaseStorageModule(conversationUser!!, conversationToken)
|
databaseStorageModule = DatabaseStorageModule(conversationUser!!, conversationToken)
|
||||||
@ -327,7 +331,7 @@ class ConversationInfoController(args: Bundle) : BaseController(), FlexibleAdapt
|
|||||||
super.onRestoreViewState(view, savedViewState)
|
super.onRestoreViewState(view, savedViewState)
|
||||||
if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) {
|
if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) {
|
||||||
//Dialog won't be restarted automatically, so we need to call this method.
|
//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)
|
showLovelyDialog(LovelySaveStateHandler.getSavedDialogId(savedViewState), savedViewState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ public class ConversationsListController extends BaseController implements Searc
|
|||||||
@Inject
|
@Inject
|
||||||
AppPreferences appPreferences;
|
AppPreferences appPreferences;
|
||||||
|
|
||||||
@BindView(R.id.recycler_view)
|
@BindView(R.id.recyclerView)
|
||||||
RecyclerView recyclerView;
|
RecyclerView recyclerView;
|
||||||
|
|
||||||
@BindView(R.id.swipeRefreshLayoutView)
|
@BindView(R.id.swipeRefreshLayoutView)
|
||||||
@ -480,7 +480,7 @@ public class ConversationsListController extends BaseController implements Searc
|
|||||||
searchQuery = savedViewState.getString(KEY_SEARCH_QUERY, "");
|
searchQuery = savedViewState.getString(KEY_SEARCH_QUERY, "");
|
||||||
if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) {
|
if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) {
|
||||||
//Dialog won't be restarted automatically, so we need to call this method.
|
//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);
|
showLovelyDialog(LovelySaveStateHandler.getSavedDialogId(savedViewState), savedViewState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ public class RingtoneSelectionController extends BaseController implements Flexi
|
|||||||
|
|
||||||
private static final String TAG = "RingtoneSelectionController";
|
private static final String TAG = "RingtoneSelectionController";
|
||||||
|
|
||||||
@BindView(R.id.recycler_view)
|
@BindView(R.id.recyclerView)
|
||||||
RecyclerView recyclerView;
|
RecyclerView recyclerView;
|
||||||
|
|
||||||
@BindView(R.id.swipe_refresh_layout)
|
@BindView(R.id.swipe_refresh_layout)
|
||||||
|
@ -346,7 +346,7 @@ public class SettingsController extends BaseController {
|
|||||||
super.onRestoreViewState(view, savedViewState);
|
super.onRestoreViewState(view, savedViewState);
|
||||||
if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) {
|
if (LovelySaveStateHandler.wasDialogOnScreen(savedViewState)) {
|
||||||
//Dialog won't be restarted automatically, so we need to call this method.
|
//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);
|
showLovelyDialog(LovelySaveStateHandler.getSavedDialogId(savedViewState), savedViewState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ public class SwitchAccountController extends BaseController {
|
|||||||
@Inject
|
@Inject
|
||||||
UserUtils userUtils;
|
UserUtils userUtils;
|
||||||
|
|
||||||
@BindView(R.id.recycler_view)
|
@BindView(R.id.recyclerView)
|
||||||
RecyclerView recyclerView;
|
RecyclerView recyclerView;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -21,10 +21,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.nextcloud.talk.controllers.base
|
package com.nextcloud.talk.controllers.base
|
||||||
|
|
||||||
|
import android.content.ComponentCallbacks
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
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.nextcloud.talk.utils.preferences.AppPreferences
|
||||||
import com.uber.autodispose.lifecycle.LifecycleScopeProvider
|
import com.uber.autodispose.lifecycle.LifecycleScopeProvider
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.koin.core.Koin
|
import org.koin.android.ext.android.inject
|
||||||
import org.koin.core.KoinComponent
|
|
||||||
import org.koin.core.inject
|
|
||||||
import org.koin.core.qualifier.named
|
|
||||||
import org.koin.core.scope.Scope
|
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AutoInjector(NextcloudTalkApplication::class)
|
@AutoInjector(NextcloudTalkApplication::class)
|
||||||
abstract class BaseController : ButterKnifeController(), KoinComponent {
|
abstract class BaseController : ButterKnifeController(), ComponentCallbacks {
|
||||||
|
|
||||||
val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this)
|
val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this)
|
||||||
|
|
||||||
@ -75,10 +71,6 @@ abstract class BaseController : ButterKnifeController(), KoinComponent {
|
|||||||
return actionBarProvider?.supportActionBar
|
return actionBarProvider?.supportActionBar
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getKoin(): Koin {
|
|
||||||
return super.getKoin()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
android.R.id.home -> {
|
android.R.id.home -> {
|
||||||
@ -112,7 +104,6 @@ abstract class BaseController : ButterKnifeController(), KoinComponent {
|
|||||||
|
|
||||||
override fun onAttach(view: View) {
|
override fun onAttach(view: View) {
|
||||||
super.onAttach(view)
|
super.onAttach(view)
|
||||||
eventBus.register(this)
|
|
||||||
|
|
||||||
setTitle()
|
setTitle()
|
||||||
if (actionBar != null) {
|
if (actionBar != null) {
|
||||||
@ -121,7 +112,6 @@ abstract class BaseController : ButterKnifeController(), KoinComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetach(view: View) {
|
override fun onDetach(view: View) {
|
||||||
eventBus.unregister(this)
|
|
||||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||||
super.onDetach(view)
|
super.onDetach(view)
|
||||||
@ -159,6 +149,12 @@ abstract class BaseController : ButterKnifeController(), KoinComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLowMemory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val TAG = "BaseController"
|
private val TAG = "BaseController"
|
||||||
|
@ -31,7 +31,7 @@ import com.bluelinelabs.conductor.Controller
|
|||||||
|
|
||||||
abstract class ButterKnifeController : Controller {
|
abstract class ButterKnifeController : Controller {
|
||||||
|
|
||||||
private var unbinder: Unbinder? = null
|
protected var unbinder: Unbinder? = null
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
|
@ -67,7 +67,7 @@ import org.parceler.Parcels;
|
|||||||
@AutoInjector(NextcloudTalkApplication.class)
|
@AutoInjector(NextcloudTalkApplication.class)
|
||||||
public class CallMenuController extends BaseController
|
public class CallMenuController extends BaseController
|
||||||
implements FlexibleAdapter.OnItemClickListener {
|
implements FlexibleAdapter.OnItemClickListener {
|
||||||
@BindView(R.id.recycler_view)
|
@BindView(R.id.recyclerView)
|
||||||
RecyclerView recyclerView;
|
RecyclerView recyclerView;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -27,8 +27,8 @@ import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository
|
|||||||
import com.nextcloud.talk.utils.ApiUtils
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
|
|
||||||
class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : NextcloudTalkRepository {
|
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),
|
return apiService.getConversations(ApiUtils.getCredentials(user.username, user.token),
|
||||||
ApiUtils.getUrlForGetRooms(user.baseUrl)).ocs.data
|
ApiUtils.getUrlForGetRooms(user.baseUrl)).ocs.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,7 @@ import okhttp3.OkHttpClient
|
|||||||
import okhttp3.internal.tls.OkHostnameVerifier
|
import okhttp3.internal.tls.OkHostnameVerifier
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import okhttp3.logging.HttpLoggingInterceptor.Logger
|
import okhttp3.logging.HttpLoggingInterceptor.Logger
|
||||||
|
import org.koin.android.ext.koin.androidApplication
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
@ -70,8 +71,16 @@ import javax.net.ssl.X509KeyManager
|
|||||||
val NetworkModule = module {
|
val NetworkModule = module {
|
||||||
single { createService(get()) }
|
single { createService(get()) }
|
||||||
single { createRetrofit(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()) }
|
single { createOkHttpClient(androidContext(), get(), get(), get(), get(), get(), get(), get()) }
|
||||||
factory { createGetConversationsUseCase(get(), get()) }
|
|
||||||
|
single { createNextcloudTalkRepository(get()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createCookieManager(): CookieManager {
|
fun createCookieManager(): CookieManager {
|
||||||
@ -242,9 +251,3 @@ fun createNextcloudTalkRepository(apiService: ApiService): NextcloudTalkReposito
|
|||||||
return NextcloudTalkRepositoryImpl(apiService)
|
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
|
import com.nextcloud.talk.models.json.conversations.Conversation
|
||||||
|
|
||||||
interface NextcloudTalkRepository {
|
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) {
|
) : UseCase<List<Conversation>, Any?>(apiErrorHandler) {
|
||||||
|
|
||||||
override suspend fun run(params: Any?): List<Conversation> {
|
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
|
abstract suspend fun run(params: Params? = null): Type
|
||||||
lateinit var user: UserEntity
|
lateinit var user: UserEntity
|
||||||
|
fun isUserInitialized() = ::user.isInitialized
|
||||||
|
|
||||||
fun invoke(
|
fun invoke(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
|
@ -18,29 +18,19 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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.lifecycle.ViewModel
|
||||||
import androidx.annotation.LayoutRes
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import autodagger.AutoInjector
|
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
import com.nextcloud.talk.utils.database.user.UserUtils
|
||||||
import com.nextcloud.talk.controllers.base.BaseController
|
|
||||||
|
|
||||||
@AutoInjector(NextcloudTalkApplication::class)
|
class ConversationListViewModelFactory constructor(
|
||||||
abstract class BaseView : BaseController() {
|
private val conversationsUseCase: GetConversationsUseCase,
|
||||||
|
private val userUtils: UserUtils
|
||||||
|
): ViewModelProvider.Factory {
|
||||||
|
|
||||||
override fun onDetach(view: View) {
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||||
super.onDetach(view)
|
return ConversationsListViewModel(conversationsUseCase, userUtils) as T;
|
||||||
getPresenter().stop()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
|
||||||
getPresenter().destroy()
|
|
||||||
super.onDestroy()
|
|
||||||
}
|
|
||||||
|
|
||||||
@LayoutRes
|
|
||||||
protected abstract fun getLayoutId(): Int
|
|
||||||
|
|
||||||
protected abstract fun getPresenter(): MvpPresenter
|
|
||||||
}
|
|
@ -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
|
package com.nextcloud.talk.newarch.conversationsList.mvp
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
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 val disposables: CompositeDisposable = CompositeDisposable()
|
||||||
protected var view: V? = null
|
|
||||||
private set
|
|
||||||
|
|
||||||
fun start(view: V) {
|
val backgroundAndUIScope = CoroutineScope(
|
||||||
this.view = view
|
Job() + Dispatchers.Main
|
||||||
}
|
)
|
||||||
|
|
||||||
override fun stop() {
|
val backgroundScope = CoroutineScope(
|
||||||
this.view = null
|
Job()
|
||||||
}
|
)
|
||||||
|
|
||||||
override fun destroy() {
|
override fun onCleared() {
|
||||||
disposables.clear()
|
super.onCleared()
|
||||||
}
|
disposables.clear()
|
||||||
|
backgroundAndUIScope.cancel()
|
||||||
|
backgroundScope.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
@ -18,9 +18,10 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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 {
|
enum class ViewState {
|
||||||
fun stop()
|
LOADING,
|
||||||
fun destroy()
|
LOADED,
|
||||||
|
FAILED
|
||||||
}
|
}
|
@ -18,17 +18,16 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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 {
|
fun RecyclerView.initRecyclerView(
|
||||||
interface View {
|
layoutManager: RecyclerView.LayoutManager,
|
||||||
fun onLoadConversationsSuccess(conversations: List<Conversation>)
|
adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>,
|
||||||
fun onLoadConversationsFailure(throwable: Throwable)
|
hasFixedSize: Boolean = true
|
||||||
}
|
) {
|
||||||
|
this.layoutManager = layoutManager
|
||||||
interface Presenter {
|
this.adapter = adapter
|
||||||
fun loadConversations()
|
setHasFixedSize(hasFixedSize)
|
||||||
}
|
}
|
||||||
}
|
|
@ -27,31 +27,41 @@ import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
|||||||
import com.nextcloud.talk.controllers.ChatController
|
import com.nextcloud.talk.controllers.ChatController
|
||||||
|
|
||||||
object ConductorRemapping {
|
object ConductorRemapping {
|
||||||
fun remapChatController(router: Router, internalUserId: Long, roomTokenOrId: String, bundle: Bundle, replaceTop: Boolean) {
|
fun remapChatController(
|
||||||
val tag = "$internalUserId@$roomTokenOrId"
|
router: Router,
|
||||||
if (router.getControllerWithTag(tag) != null) {
|
internalUserId: Long,
|
||||||
val backstack = router.backstack
|
roomTokenOrId: String,
|
||||||
var routerTransaction: RouterTransaction? = null
|
bundle: Bundle,
|
||||||
for (i in 0 until router.backstackSize) {
|
replaceTop: Boolean
|
||||||
if (tag == backstack[i].tag()) {
|
) {
|
||||||
routerTransaction = backstack[i]
|
val tag = "$internalUserId@$roomTokenOrId"
|
||||||
backstack.remove(routerTransaction)
|
if (router.getControllerWithTag(tag) != null) {
|
||||||
break
|
val backstack = router.backstack
|
||||||
}
|
var routerTransaction: RouterTransaction? = null
|
||||||
}
|
for (i in 0 until router.backstackSize) {
|
||||||
|
if (tag == backstack[i].tag()) {
|
||||||
backstack.add(routerTransaction)
|
routerTransaction = backstack[i]
|
||||||
router.setBackstack(backstack, HorizontalChangeHandler())
|
backstack.remove(routerTransaction)
|
||||||
} else {
|
break
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
Log.d(TAG, "No such algorithm");
|
Log.d(TAG, "No such algorithm");
|
||||||
} catch (IOException e) {
|
} 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) {
|
} catch (InvalidKeyException e) {
|
||||||
Log.d(TAG, "Invalid key while trying to verify");
|
Log.d(TAG, "Invalid key while trying to verify");
|
||||||
} catch (SignatureException e) {
|
} catch (SignatureException e) {
|
||||||
|
@ -125,7 +125,7 @@ public class MagicAudioManager {
|
|||||||
// Tablet devices (e.g. Nexus 7) does not support proximity sensors.
|
// Tablet devices (e.g. Nexus 7) does not support proximity sensors.
|
||||||
// Note that, the sensor will not be active until start() has been called.
|
// Note that, the sensor will not be active until start() has been called.
|
||||||
proximitySensor = MagicProximitySensor.create(context, new Runnable() {
|
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),
|
// Example: user holds his hand over the device (closer than ~5 cm),
|
||||||
// or removes his hand from the device.
|
// or removes his hand from the device.
|
||||||
public void run() {
|
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".
|
* e.g. from "NEAR to FAR" or from "FAR to NEAR".
|
||||||
*/
|
*/
|
||||||
private void onProximitySensorChangedState() {
|
private void onProximitySensorChangedState() {
|
||||||
@ -205,7 +205,7 @@ public class MagicAudioManager {
|
|||||||
this.audioManagerEvents = audioManagerEvents;
|
this.audioManagerEvents = audioManagerEvents;
|
||||||
amState = AudioManagerState.RUNNING;
|
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();
|
savedAudioMode = audioManager.getMode();
|
||||||
savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
|
savedIsSpeakerPhoneOn = audioManager.isSpeakerphoneOn();
|
||||||
savedIsMicrophoneMute = audioManager.isMicrophoneMute();
|
savedIsMicrophoneMute = audioManager.isMicrophoneMute();
|
||||||
@ -294,7 +294,7 @@ public class MagicAudioManager {
|
|||||||
Log.d(TAG, "stop");
|
Log.d(TAG, "stop");
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
if (amState != AudioManagerState.RUNNING) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
amState = AudioManagerState.UNINITIALIZED;
|
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) {
|
private void setMicrophoneMute(boolean on) {
|
||||||
boolean wasMuted = audioManager.isMicrophoneMute();
|
boolean wasMuted = audioManager.isMicrophoneMute();
|
||||||
@ -445,7 +445,7 @@ public class MagicAudioManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the current earpiece state.
|
* Gets the current earpiece viewState.
|
||||||
*/
|
*/
|
||||||
private boolean hasEarpiece() {
|
private boolean hasEarpiece() {
|
||||||
return magicContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
|
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.
|
* 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() {
|
public void updateAudioDeviceState() {
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
Log.d(TAG, "--- updateAudioDeviceState: "
|
Log.d(TAG, "--- updateAudioDeviceState: "
|
||||||
+ "wired headset=" + hasWiredHeadset + ", "
|
+ "wired headset=" + hasWiredHeadset + ", "
|
||||||
+ "BT state=" + bluetoothManager.getState());
|
+ "BT viewState=" + bluetoothManager.getState());
|
||||||
Log.d(TAG, "Device status: "
|
Log.d(TAG, "Device status: "
|
||||||
+ "available=" + audioDevices + ", "
|
+ "available=" + audioDevices + ", "
|
||||||
+ "selected=" + selectedAudioDevice + ", "
|
+ "selected=" + selectedAudioDevice + ", "
|
||||||
+ "user selected=" + userSelectedAudioDevice);
|
+ "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.
|
// 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
|
if (bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_AVAILABLE
|
||||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE
|
|| bluetoothManager.getState() == MagicBluetoothManager.State.HEADSET_UNAVAILABLE
|
||||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_DISCONNECTING) {
|
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_DISCONNECTING) {
|
||||||
@ -521,7 +521,7 @@ public class MagicAudioManager {
|
|||||||
newAudioDevices.add(AudioDevice.EARPIECE);
|
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);
|
boolean audioDeviceSetUpdated = !audioDevices.equals(newAudioDevices);
|
||||||
// Update the existing audio device set.
|
// Update the existing audio device set.
|
||||||
audioDevices = newAudioDevices;
|
audioDevices = newAudioDevices;
|
||||||
@ -562,7 +562,7 @@ public class MagicAudioManager {
|
|||||||
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED) {
|
|| bluetoothManager.getState() == MagicBluetoothManager.State.SCO_CONNECTED) {
|
||||||
Log.d(TAG, "Need BT audio: start=" + needBluetoothAudioStart + ", "
|
Log.d(TAG, "Need BT audio: start=" + needBluetoothAudioStart + ", "
|
||||||
+ "stop=" + needBluetoothAudioStop + ", "
|
+ "stop=" + needBluetoothAudioStop + ", "
|
||||||
+ "BT state=" + bluetoothManager.getState());
|
+ "BT viewState=" + bluetoothManager.getState());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start or stop Bluetooth SCO connection given states set earlier.
|
// Start or stop Bluetooth SCO connection given states set earlier.
|
||||||
@ -624,7 +624,7 @@ public class MagicAudioManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AudioManager state.
|
* AudioManager viewState.
|
||||||
*/
|
*/
|
||||||
public enum AudioManagerState {
|
public enum AudioManagerState {
|
||||||
UNINITIALIZED,
|
UNINITIALIZED,
|
||||||
@ -649,7 +649,7 @@ public class MagicAudioManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
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);
|
// int microphone = intent.getIntExtra("microphone", HAS_NO_MIC);
|
||||||
// String name = intent.getStringExtra("name");
|
// String name = intent.getStringExtra("name");
|
||||||
hasWiredHeadset = (state == STATE_PLUGGED);
|
hasWiredHeadset = (state == STATE_PLUGGED);
|
||||||
|
@ -98,7 +98,7 @@ public class MagicBluetoothManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the internal state.
|
* Returns the internal viewState.
|
||||||
*/
|
*/
|
||||||
public State getState() {
|
public State getState() {
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
@ -110,14 +110,14 @@ public class MagicBluetoothManager {
|
|||||||
/**
|
/**
|
||||||
* Activates components required to detect Bluetooth devices and to enable
|
* Activates components required to detect Bluetooth devices and to enable
|
||||||
* BT SCO (audio is routed via BT SCO) for the headset profile. The end
|
* 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
|
* viewState will be HEADSET_UNAVAILABLE but a viewState machine has started which
|
||||||
* will start a state change sequence where the final outcome depends on
|
* will start a viewState change sequence where the final outcome depends on
|
||||||
* if/when the BT headset is enabled.
|
* 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:
|
* is connected and enabled:
|
||||||
* UNINITIALIZED --> HEADSET_UNAVAILABLE --> HEADSET_AVAILABLE -->
|
* UNINITIALIZED --> HEADSET_UNAVAILABLE --> HEADSET_AVAILABLE -->
|
||||||
* SCO_CONNECTING --> SCO_CONNECTED <==> audio is now routed via BT SCO.
|
* 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.
|
* change.
|
||||||
*/
|
*/
|
||||||
public void start() {
|
public void start() {
|
||||||
@ -128,7 +128,7 @@ public class MagicBluetoothManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (bluetoothState != State.UNINITIALIZED) {
|
if (bluetoothState != State.UNINITIALIZED) {
|
||||||
Log.w(TAG, "Invalid BT state");
|
Log.w(TAG, "Invalid BT viewState");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bluetoothHeadset = null;
|
bluetoothHeadset = null;
|
||||||
@ -155,16 +155,16 @@ public class MagicBluetoothManager {
|
|||||||
}
|
}
|
||||||
// Register receivers for BluetoothHeadset change notifications.
|
// Register receivers for BluetoothHeadset change notifications.
|
||||||
IntentFilter bluetoothHeadsetFilter = new IntentFilter();
|
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);
|
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);
|
bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
|
||||||
registerReceiver(bluetoothHeadsetReceiver, bluetoothHeadsetFilter);
|
registerReceiver(bluetoothHeadsetReceiver, bluetoothHeadsetFilter);
|
||||||
Log.d(TAG, "HEADSET profile state: "
|
Log.d(TAG, "HEADSET profile viewState: "
|
||||||
+ stateToString(bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET)));
|
+ stateToString(bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET)));
|
||||||
Log.d(TAG, "Bluetooth proxy for headset profile has started");
|
Log.d(TAG, "Bluetooth proxy for headset profile has started");
|
||||||
bluetoothState = State.HEADSET_UNAVAILABLE;
|
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() {
|
public void stop() {
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
Log.d(TAG, "stop: BT state=" + bluetoothState);
|
Log.d(TAG, "stop: BT viewState=" + bluetoothState);
|
||||||
if (bluetoothAdapter == null) {
|
if (bluetoothAdapter == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -191,7 +191,7 @@ public class MagicBluetoothManager {
|
|||||||
bluetoothAdapter = null;
|
bluetoothAdapter = null;
|
||||||
bluetoothDevice = null;
|
bluetoothDevice = null;
|
||||||
bluetoothState = State.UNINITIALIZED;
|
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() {
|
public boolean startScoAudio() {
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
Log.d(TAG, "startSco: BT state=" + bluetoothState + ", "
|
Log.d(TAG, "startSco: BT viewState=" + bluetoothState + ", "
|
||||||
+ "attempts: " + scoConnectionAttempts + ", "
|
+ "attempts: " + scoConnectionAttempts + ", "
|
||||||
+ "SCO is on: " + isScoOn());
|
+ "SCO is on: " + isScoOn());
|
||||||
if (scoConnectionAttempts >= MAX_SCO_CONNECTION_ATTEMPTS) {
|
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...");
|
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
|
// 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
|
// 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;
|
bluetoothState = State.SCO_CONNECTING;
|
||||||
audioManager.startBluetoothSco();
|
audioManager.startBluetoothSco();
|
||||||
audioManager.setBluetoothScoOn(true);
|
audioManager.setBluetoothScoOn(true);
|
||||||
scoConnectionAttempts++;
|
scoConnectionAttempts++;
|
||||||
startTimer();
|
startTimer();
|
||||||
Log.d(TAG, "startScoAudio done: BT state=" + bluetoothState + ", "
|
Log.d(TAG, "startScoAudio done: BT viewState=" + bluetoothState + ", "
|
||||||
+ "SCO is on: " + isScoOn());
|
+ "SCO is on: " + isScoOn());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -240,7 +240,7 @@ public class MagicBluetoothManager {
|
|||||||
*/
|
*/
|
||||||
public void stopScoAudio() {
|
public void stopScoAudio() {
|
||||||
ThreadUtils.checkIsOnMainThread();
|
ThreadUtils.checkIsOnMainThread();
|
||||||
Log.d(TAG, "stopScoAudio: BT state=" + bluetoothState + ", "
|
Log.d(TAG, "stopScoAudio: BT viewState=" + bluetoothState + ", "
|
||||||
+ "SCO is on: " + isScoOn());
|
+ "SCO is on: " + isScoOn());
|
||||||
if (bluetoothState != State.SCO_CONNECTING && bluetoothState != State.SCO_CONNECTED) {
|
if (bluetoothState != State.SCO_CONNECTING && bluetoothState != State.SCO_CONNECTED) {
|
||||||
return;
|
return;
|
||||||
@ -249,14 +249,14 @@ public class MagicBluetoothManager {
|
|||||||
audioManager.stopBluetoothSco();
|
audioManager.stopBluetoothSco();
|
||||||
audioManager.setBluetoothScoOn(false);
|
audioManager.setBluetoothScoOn(false);
|
||||||
bluetoothState = State.SCO_DISCONNECTING;
|
bluetoothState = State.SCO_DISCONNECTING;
|
||||||
Log.d(TAG, "stopScoAudio done: BT state=" + bluetoothState + ", "
|
Log.d(TAG, "stopScoAudio done: BT viewState=" + bluetoothState + ", "
|
||||||
+ "SCO is on: " + isScoOn());
|
+ "SCO is on: " + isScoOn());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use the BluetoothHeadset proxy object (controls the Bluetooth Headset
|
* Use the BluetoothHeadset proxy object (controls the Bluetooth Headset
|
||||||
* Service via IPC) to update the list of connected devices for the 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
|
* HEADSET_AVAILABLE and |bluetoothDevice| will be mapped to the connected
|
||||||
* device if available.
|
* device if available.
|
||||||
*/
|
*/
|
||||||
@ -266,7 +266,7 @@ public class MagicBluetoothManager {
|
|||||||
}
|
}
|
||||||
Log.d(TAG, "updateDevice");
|
Log.d(TAG, "updateDevice");
|
||||||
// Get connected devices for the headset profile. Returns the set of
|
// 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.
|
// is just a thin wrapper for a Bluetooth hardware address.
|
||||||
List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices();
|
List<BluetoothDevice> devices = bluetoothHeadset.getConnectedDevices();
|
||||||
if (devices.isEmpty()) {
|
if (devices.isEmpty()) {
|
||||||
@ -279,10 +279,10 @@ public class MagicBluetoothManager {
|
|||||||
bluetoothState = State.HEADSET_AVAILABLE;
|
bluetoothState = State.HEADSET_AVAILABLE;
|
||||||
Log.d(TAG, "Connected bluetooth headset: "
|
Log.d(TAG, "Connected bluetooth headset: "
|
||||||
+ "name=" + bluetoothDevice.getName() + ", "
|
+ "name=" + bluetoothDevice.getName() + ", "
|
||||||
+ "state=" + stateToString(bluetoothHeadset.getConnectionState(bluetoothDevice))
|
+ "viewState=" + stateToString(bluetoothHeadset.getConnectionState(bluetoothDevice))
|
||||||
+ ", SCO audio=" + bluetoothHeadset.isAudioConnected(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")
|
@SuppressLint("HardwareIds")
|
||||||
protected void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) {
|
protected void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) {
|
||||||
Log.d(TAG, "BluetoothAdapter: "
|
Log.d(TAG, "BluetoothAdapter: "
|
||||||
+ "enabled=" + localAdapter.isEnabled() + ", "
|
+ "enabled=" + localAdapter.isEnabled() + ", "
|
||||||
+ "state=" + stateToString(localAdapter.getState()) + ", "
|
+ "viewState=" + stateToString(localAdapter.getState()) + ", "
|
||||||
+ "name=" + localAdapter.getName() + ", "
|
+ "name=" + localAdapter.getName() + ", "
|
||||||
+ "address=" + localAdapter.getAddress());
|
+ "address=" + localAdapter.getAddress());
|
||||||
// Log the set of BluetoothDevice objects that are bonded (paired) to the local adapter.
|
// 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) {
|
if (bluetoothState == State.UNINITIALIZED || bluetoothHeadset == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.d(TAG, "bluetoothTimeout: BT state=" + bluetoothState + ", "
|
Log.d(TAG, "bluetoothTimeout: BT viewState=" + bluetoothState + ", "
|
||||||
+ "attempts: " + scoConnectionAttempts + ", "
|
+ "attempts: " + scoConnectionAttempts + ", "
|
||||||
+ "SCO is on: " + isScoOn());
|
+ "SCO is on: " + isScoOn());
|
||||||
if (bluetoothState != State.SCO_CONNECTING) {
|
if (bluetoothState != State.SCO_CONNECTING) {
|
||||||
@ -385,7 +385,7 @@ public class MagicBluetoothManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (scoConnected) {
|
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;
|
bluetoothState = State.SCO_CONNECTED;
|
||||||
scoConnectionAttempts = 0;
|
scoConnectionAttempts = 0;
|
||||||
} else {
|
} else {
|
||||||
@ -394,7 +394,7 @@ public class MagicBluetoothManager {
|
|||||||
stopScoAudio();
|
stopScoAudio();
|
||||||
}
|
}
|
||||||
updateAudioDeviceState();
|
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 {
|
public enum State {
|
||||||
// Bluetooth is not available; no adapter or Bluetooth is off.
|
// Bluetooth is not available; no adapter or Bluetooth is off.
|
||||||
UNINITIALIZED,
|
UNINITIALIZED,
|
||||||
@ -461,17 +461,17 @@ public class MagicBluetoothManager {
|
|||||||
private class BluetoothServiceListener implements BluetoothProfile.ServiceListener {
|
private class BluetoothServiceListener implements BluetoothProfile.ServiceListener {
|
||||||
@Override
|
@Override
|
||||||
// Called to notify the client when the proxy object has been connected to the service.
|
// 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.
|
// connection and perform other operations that are relevant to the headset profile.
|
||||||
public void onServiceConnected(int profile, BluetoothProfile proxy) {
|
public void onServiceConnected(int profile, BluetoothProfile proxy) {
|
||||||
if (profile != BluetoothProfile.HEADSET || bluetoothState == State.UNINITIALIZED) {
|
if (profile != BluetoothProfile.HEADSET || bluetoothState == State.UNINITIALIZED) {
|
||||||
return;
|
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.
|
// Android only supports one connected Bluetooth Headset at a time.
|
||||||
bluetoothHeadset = (BluetoothHeadset) proxy;
|
bluetoothHeadset = (BluetoothHeadset) proxy;
|
||||||
updateAudioDeviceState();
|
updateAudioDeviceState();
|
||||||
Log.d(TAG, "onServiceConnected done: BT state=" + bluetoothState);
|
Log.d(TAG, "onServiceConnected done: BT viewState=" + bluetoothState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -480,18 +480,18 @@ public class MagicBluetoothManager {
|
|||||||
if (profile != BluetoothProfile.HEADSET || bluetoothState == State.UNINITIALIZED) {
|
if (profile != BluetoothProfile.HEADSET || bluetoothState == State.UNINITIALIZED) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.d(TAG, "BluetoothServiceListener.onServiceDisconnected: BT state=" + bluetoothState);
|
Log.d(TAG, "BluetoothServiceListener.onServiceDisconnected: BT viewState=" + bluetoothState);
|
||||||
stopScoAudio();
|
stopScoAudio();
|
||||||
bluetoothHeadset = null;
|
bluetoothHeadset = null;
|
||||||
bluetoothDevice = null;
|
bluetoothDevice = null;
|
||||||
bluetoothState = State.HEADSET_UNAVAILABLE;
|
bluetoothState = State.HEADSET_UNAVAILABLE;
|
||||||
updateAudioDeviceState();
|
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.
|
// 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 {
|
private class BluetoothHeadsetBroadcastReceiver extends BroadcastReceiver {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
@ -499,7 +499,7 @@ public class MagicBluetoothManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final String action = intent.getAction();
|
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
|
// change does not tell us anything about whether we're streaming
|
||||||
// audio to BT over SCO. Typically received when user turns on a BT
|
// audio to BT over SCO. Typically received when user turns on a BT
|
||||||
// headset while audio is active using another audio device.
|
// headset while audio is active using another audio device.
|
||||||
@ -510,7 +510,7 @@ public class MagicBluetoothManager {
|
|||||||
+ "a=ACTION_CONNECTION_STATE_CHANGED, "
|
+ "a=ACTION_CONNECTION_STATE_CHANGED, "
|
||||||
+ "s=" + stateToString(state) + ", "
|
+ "s=" + stateToString(state) + ", "
|
||||||
+ "sb=" + isInitialStickyBroadcast() + ", "
|
+ "sb=" + isInitialStickyBroadcast() + ", "
|
||||||
+ "BT state: " + bluetoothState);
|
+ "BT viewState: " + bluetoothState);
|
||||||
if (state == BluetoothHeadset.STATE_CONNECTED) {
|
if (state == BluetoothHeadset.STATE_CONNECTED) {
|
||||||
scoConnectionAttempts = 0;
|
scoConnectionAttempts = 0;
|
||||||
updateAudioDeviceState();
|
updateAudioDeviceState();
|
||||||
@ -523,7 +523,7 @@ public class MagicBluetoothManager {
|
|||||||
stopScoAudio();
|
stopScoAudio();
|
||||||
updateAudioDeviceState();
|
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.
|
// Typically received after call to startScoAudio() has finalized.
|
||||||
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
|
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
|
||||||
final int state = intent.getIntExtra(
|
final int state = intent.getIntExtra(
|
||||||
@ -532,7 +532,7 @@ public class MagicBluetoothManager {
|
|||||||
+ "a=ACTION_AUDIO_STATE_CHANGED, "
|
+ "a=ACTION_AUDIO_STATE_CHANGED, "
|
||||||
+ "s=" + stateToString(state) + ", "
|
+ "s=" + stateToString(state) + ", "
|
||||||
+ "sb=" + isInitialStickyBroadcast() + ", "
|
+ "sb=" + isInitialStickyBroadcast() + ", "
|
||||||
+ "BT state: " + bluetoothState);
|
+ "BT viewState: " + bluetoothState);
|
||||||
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
|
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
|
||||||
cancelTimer();
|
cancelTimer();
|
||||||
if (bluetoothState == State.SCO_CONNECTING) {
|
if (bluetoothState == State.SCO_CONNECTING) {
|
||||||
@ -541,7 +541,7 @@ public class MagicBluetoothManager {
|
|||||||
scoConnectionAttempts = 0;
|
scoConnectionAttempts = 0;
|
||||||
updateAudioDeviceState();
|
updateAudioDeviceState();
|
||||||
} else {
|
} 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) {
|
} else if (state == BluetoothHeadset.STATE_AUDIO_CONNECTING) {
|
||||||
Log.d(TAG, "+++ Bluetooth audio SCO is now connecting...");
|
Log.d(TAG, "+++ Bluetooth audio SCO is now connecting...");
|
||||||
@ -554,7 +554,7 @@ public class MagicBluetoothManager {
|
|||||||
updateAudioDeviceState();
|
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() {
|
boolean sensorReportsNearState() {
|
||||||
threadChecker.checkIsOnValidThread();
|
threadChecker.checkIsOnValidThread();
|
||||||
@ -124,15 +124,15 @@ public class MagicProximitySensor implements SensorEventListener {
|
|||||||
// avoid blocking.
|
// avoid blocking.
|
||||||
float distanceInCentimeters = event.values[0];
|
float distanceInCentimeters = event.values[0];
|
||||||
if (distanceInCentimeters < proximitySensor.getMaximumRange()) {
|
if (distanceInCentimeters < proximitySensor.getMaximumRange()) {
|
||||||
Log.d(TAG, "Proximity sensor => NEAR state");
|
Log.d(TAG, "Proximity sensor => NEAR viewState");
|
||||||
lastStateReportIsNear = true;
|
lastStateReportIsNear = true;
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Proximity sensor => FAR state");
|
Log.d(TAG, "Proximity sensor => FAR viewState");
|
||||||
lastStateReportIsNear = false;
|
lastStateReportIsNear = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Report about new state to listening client. Client can then call
|
// Report about new viewState to listening client. Client can then call
|
||||||
// sensorReportsNearState() to query the current state (NEAR or FAR).
|
// sensorReportsNearState() to query the current viewState (NEAR or FAR).
|
||||||
if (onSensorStateListener != null) {
|
if (onSensorStateListener != null) {
|
||||||
onSensorStateListener.run();
|
onSensorStateListener.run();
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_above="@id/bottom_navigation"
|
android:layout_above="@id/bottom_navigation"
|
||||||
@ -53,7 +53,7 @@
|
|||||||
layout="@layout/fast_scroller"
|
layout="@layout/fast_scroller"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_alignTop="@id/recycler_view"
|
android:layout_alignTop="@id/recyclerView"
|
||||||
android:layout_alignBottom="@id/recycler_view" />
|
android:layout_alignBottom="@id/recyclerView" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
@ -25,7 +25,7 @@
|
|||||||
android:background="@color/nc_white_color">
|
android:background="@color/nc_white_color">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:listitem="@layout/rv_item_conversation" />
|
tools:listitem="@layout/rv_item_conversation" />
|
||||||
|
@ -106,7 +106,7 @@
|
|||||||
tools:ignore="UnknownIdInLayout">
|
tools:ignore="UnknownIdInLayout">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
tools:listitem="@layout/rv_item_contact" />
|
tools:listitem="@layout/rv_item_contact" />
|
||||||
|
@ -75,7 +75,7 @@
|
|||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:listitem="@layout/rv_item_conversation" />
|
tools:listitem="@layout/rv_item_conversation" />
|
||||||
@ -90,6 +90,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
android:layout_margin="16dp"
|
android:layout_margin="16dp"
|
||||||
|
android:visibility="gone"
|
||||||
app:srcCompat="@drawable/ic_add_white_24px" />
|
app:srcCompat="@drawable/ic_add_white_24px" />
|
||||||
|
|
||||||
<include layout="@layout/fast_scroller" />
|
<include layout="@layout/fast_scroller" />
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/recycler_view"
|
android:id="@+id/recyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:listitem="@layout/rv_item_conversation" />
|
tools:listitem="@layout/rv_item_conversation" />
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
android:title="@string/nc_search"
|
android:title="@string/nc_search"
|
||||||
android:icon="@drawable/ic_search_white_24dp"
|
android:icon="@drawable/ic_search_white_24dp"
|
||||||
app:showAsAction="collapseActionView|always"
|
app:showAsAction="collapseActionView|always"
|
||||||
|
android:visible="false"
|
||||||
android:animateLayoutChanges="true"
|
android:animateLayoutChanges="true"
|
||||||
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
app:actionViewClass="androidx.appcompat.widget.SearchView"
|
||||||
/>
|
/>
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
kotlinVersion = '1.3.50'
|
kotlin_version = '1.3.50'
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
@ -34,7 +34,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.5.1'
|
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"
|
classpath "io.realm:realm-gradle-plugin:6.0.0"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
Loading…
Reference in New Issue
Block a user