Koin implementation & EventBus cleanup

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2019-10-11 16:33:00 +02:00
parent 158f8a0d9c
commit 91e62d9de2
31 changed files with 2381 additions and 1402 deletions

View File

@ -137,6 +137,7 @@ android {
ext { ext {
workVersion = "1.0.1" workVersion = "1.0.1"
koin_version = "2.0.1"
} }
@ -150,6 +151,17 @@ configurations.all {
dependencies { dependencies {
implementation fileTree(include: ['*'], dir: 'libs') implementation fileTree(include: ['*'], dir: 'libs')
// Koin for Android
implementation "org.koin:koin-android:$koin_version"
// Koin AndroidX Scope features
implementation "org.koin:koin-androidx-scope:$koin_version"
// Koin AndroidX ViewModel features
implementation "org.koin:koin-androidx-viewmodel:$koin_version"
// Koin AndroidX Experimental features
implementation "org.koin:koin-androidx-ext:$koin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
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'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'

View File

@ -20,6 +20,7 @@
*/ */
package com.nextcloud.talk.application package com.nextcloud.talk.application
import android.app.Application
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
@ -48,6 +49,9 @@ import com.nextcloud.talk.jobs.AccountRemovalWorker
import com.nextcloud.talk.jobs.CapabilitiesWorker import com.nextcloud.talk.jobs.CapabilitiesWorker
import com.nextcloud.talk.jobs.PushRegistrationWorker import com.nextcloud.talk.jobs.PushRegistrationWorker
import com.nextcloud.talk.jobs.SignalingSettingsWorker import com.nextcloud.talk.jobs.SignalingSettingsWorker
import com.nextcloud.talk.newarch.di.module.CommunicationModule
import com.nextcloud.talk.newarch.di.module.NetworkModule
import com.nextcloud.talk.newarch.di.module.StorageModule
import com.nextcloud.talk.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
@ -62,6 +66,9 @@ import de.cotech.hw.SecurityKeyManager
import de.cotech.hw.SecurityKeyManagerConfig import de.cotech.hw.SecurityKeyManagerConfig
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import org.conscrypt.Conscrypt import org.conscrypt.Conscrypt
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
import org.webrtc.PeerConnectionFactory import org.webrtc.PeerConnectionFactory
import org.webrtc.voiceengine.WebRtcAudioManager import org.webrtc.voiceengine.WebRtcAudioManager
import org.webrtc.voiceengine.WebRtcAudioUtils import org.webrtc.voiceengine.WebRtcAudioUtils
@ -73,7 +80,7 @@ import javax.inject.Singleton
@AutoComponent(modules = [BusModule::class, ContextModule::class, DatabaseModule::class, RestModule::class, UserModule::class, ArbitraryStorageModule::class]) @AutoComponent(modules = [BusModule::class, ContextModule::class, DatabaseModule::class, RestModule::class, UserModule::class, ArbitraryStorageModule::class])
@Singleton @Singleton
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver { class NextcloudTalkApplication : Application(), LifecycleObserver {
//region Fields (components) //region Fields (components)
lateinit var componentApplication: NextcloudTalkApplicationComponent lateinit var componentApplication: NextcloudTalkApplicationComponent
private set private set
@ -182,6 +189,12 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
.userModule(UserModule()) .userModule(UserModule())
.arbitraryStorageModule(ArbitraryStorageModule()) .arbitraryStorageModule(ArbitraryStorageModule())
.build() .build()
startKoin {
androidContext(this@NextcloudTalkApplication)
androidLogger()
modules(listOf(CommunicationModule, StorageModule, NetworkModule))
}
} }
override fun attachBaseContext(base: Context) { override fun attachBaseContext(base: Context) {

View File

@ -196,7 +196,7 @@ public class BrowserController extends BaseController implements ListingInterfac
} }
@Override @Override
protected String getTitle() { public String getTitle() {
return currentPath; return currentPath;
} }

View File

@ -84,9 +84,6 @@ 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;
@ -113,18 +110,6 @@ public class AccountVerificationController extends BaseController {
} }
} }
@Override
protected void onAttach(@NonNull View view) {
super.onAttach(view);
eventBus.register(this);
}
@Override
protected void onDetach(@NonNull View view) {
super.onDetach(view);
eventBus.unregister(this);
}
@Override @Override
protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) { protected View inflateView(@NonNull LayoutInflater inflater, @NonNull ViewGroup container) {
return inflater.inflate(R.layout.controller_account_verification, container, false); return inflater.inflate(R.layout.controller_account_verification, container, false);
@ -174,7 +159,7 @@ public class AccountVerificationController extends BaseController {
ncApi.getServerStatus(queryUrl) ncApi.getServerStatus(queryUrl)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<Status>() { .subscribe(new Observer<Status>() {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {
@ -217,7 +202,7 @@ public class AccountVerificationController extends BaseController {
private void findServerTalkApp(String credentials) { private void findServerTalkApp(String credentials) {
ncApi.getRooms(credentials, ApiUtils.getUrlForGetRooms(baseUrl)) ncApi.getRooms(credentials, ApiUtils.getUrlForGetRooms(baseUrl))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<RoomsOverall>() { .subscribe(new Observer<RoomsOverall>() {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {
@ -254,7 +239,7 @@ public class AccountVerificationController extends BaseController {
userId, null, null, userId, null, null,
appPreferences.getTemporaryClientCertAlias(), null) appPreferences.getTemporaryClientCertAlias(), null)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<UserEntity>() { .subscribe(new Observer<UserEntity>() {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {
@ -294,7 +279,7 @@ public class AccountVerificationController extends BaseController {
ncApi.getUserProfile(credentials, ncApi.getUserProfile(credentials,
ApiUtils.getUrlForUserProfile(baseUrl)) ApiUtils.getUrlForUserProfile(baseUrl))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<UserProfileOverall>() { .subscribe(new Observer<UserProfileOverall>() {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {

View File

@ -192,8 +192,6 @@ public class CallController extends BaseController {
@Inject @Inject
NcApi ncApi; NcApi ncApi;
@Inject @Inject
EventBus eventBus;
@Inject
UserUtils userUtils; UserUtils userUtils;
@Inject @Inject
AppPreferences appPreferences; AppPreferences appPreferences;
@ -2259,18 +2257,6 @@ public class CallController extends BaseController {
} }
} }
@Override
protected void onAttach(@NonNull View view) {
super.onAttach(view);
eventBus.register(this);
}
@Override
protected void onDetach(@NonNull View view) {
super.onDetach(view);
eventBus.unregister(this);
}
private class MicrophoneButtonTouchListener implements View.OnTouchListener { private class MicrophoneButtonTouchListener implements View.OnTouchListener {
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")

View File

@ -200,7 +200,7 @@ public class CallNotificationController extends BaseController {
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.takeWhile(observable -> !leavingScreen) .takeWhile(observable -> !leavingScreen)
.retry(3) .retry(3)
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<ParticipantsOverall>() { .subscribe(new Observer<ParticipantsOverall>() {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {
@ -249,7 +249,7 @@ public class CallNotificationController extends BaseController {
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.retry(3) .retry(3)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<RoomsOverall>() { .subscribe(new Observer<RoomsOverall>() {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {
@ -390,19 +390,6 @@ public class CallNotificationController extends BaseController {
avatarImageView.setLayoutParams(layoutParams); avatarImageView.setLayoutParams(layoutParams);
} }
@Override
protected void onDetach(@NonNull View view) {
super.onDetach(view);
eventBus.unregister(this);
}
@Override
protected void onAttach(@NonNull View view) {
super.onAttach(view);
eventBus.register(this);
}
private void loadAvatar() { private void loadAvatar() {
switch (currentConversation.getType()) { switch (currentConversation.getType()) {
case ROOM_TYPE_ONE_TO_ONE_CALL: case ROOM_TYPE_ONE_TO_ONE_CALL:

View File

@ -213,7 +213,6 @@ public class ContactsController extends BaseController implements SearchView.OnQ
@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);
@ -283,7 +282,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
retrofitBucket.getUrl(), retrofitBucket.getQueryMap()) retrofitBucket.getUrl(), retrofitBucket.getQueryMap())
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<RoomOverall>() { .subscribe(new Observer<RoomOverall>() {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {
@ -304,7 +303,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
roomOverall.getOcs().getData().getToken())) roomOverall.getOcs().getData().getToken()))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<RoomOverall>() { .subscribe(new Observer<RoomOverall>() {
@Override @Override
@ -501,7 +500,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.retry(3) .retry(3)
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<ResponseBody>() { .subscribe(new Observer<ResponseBody>() {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {
@ -787,7 +786,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
} }
@Override @Override
protected String getTitle() { public String getTitle() {
if (!isNewConversationView && !isAddingParticipantsView) { if (!isNewConversationView && !isAddingParticipantsView) {
return getResources().getString(R.string.nc_app_name); return getResources().getString(R.string.nc_app_name);
} else { } else {
@ -855,12 +854,6 @@ public class ContactsController extends BaseController implements SearchView.OnQ
} }
} }
@Override
protected void onDetach(@NonNull View view) {
super.onDetach(view);
eventBus.unregister(this);
}
@Override @Override
public boolean onItemClick(View view, int position) { public boolean onItemClick(View view, int position) {
if (adapter.getItem(position) instanceof UserItem) { if (adapter.getItem(position) instanceof UserItem) {
@ -878,7 +871,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
retrofitBucket.getUrl(), retrofitBucket.getQueryMap()) retrofitBucket.getUrl(), retrofitBucket.getQueryMap())
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<RoomOverall>() { .subscribe(new Observer<RoomOverall>() {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {

View File

@ -130,10 +130,6 @@ class ConversationInfoController(args: Bundle) : BaseController(), FlexibleAdapt
@set:Inject @set:Inject
lateinit var ncApi: NcApi lateinit var ncApi: NcApi
@set:Inject
lateinit var context: Context
@set:Inject
lateinit var eventBus: EventBus
private val conversationToken: String? private val conversationToken: String?
private val conversationUser: UserEntity? private val conversationUser: UserEntity?
@ -185,7 +181,6 @@ class ConversationInfoController(args: Bundle) : BaseController(), FlexibleAdapt
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)
@ -306,11 +301,6 @@ class ConversationInfoController(args: Bundle) : BaseController(), FlexibleAdapt
getListOfParticipants() getListOfParticipants()
} }
override fun onDetach(view: View) {
super.onDetach(view)
eventBus.unregister(this)
}
private fun showDeleteConversationDialog(savedInstanceState: Bundle?) { private fun showDeleteConversationDialog(savedInstanceState: Bundle?) {
if (activity != null) { if (activity != null) {
LovelyStandardDialog(activity, LovelyStandardDialog.ButtonLayout.HORIZONTAL) LovelyStandardDialog(activity, LovelyStandardDialog.ButtonLayout.HORIZONTAL)

View File

@ -236,8 +236,6 @@ public class ConversationsListController extends BaseController implements Searc
@Override @Override
protected void onAttach(@NonNull View view) { protected void onAttach(@NonNull View view) {
super.onAttach(view); super.onAttach(view);
eventBus.register(this);
currentUser = userUtils.getCurrentUser(); currentUser = userUtils.getCurrentUser();
if (currentUser != null) { if (currentUser != null) {
@ -247,11 +245,6 @@ public class ConversationsListController extends BaseController implements Searc
} }
} }
@Override
protected void onDetach(@NonNull View view) {
super.onDetach(view);
eventBus.unregister(this);
}
private void initSearchView() { private void initSearchView() {
if (getActivity() != null) { if (getActivity() != null) {
@ -318,7 +311,7 @@ public class ConversationsListController extends BaseController implements Searc
ncApi.getRooms(credentials, ApiUtils.getUrlForGetRooms(currentUser.getBaseUrl())) ncApi.getRooms(credentials, ApiUtils.getUrlForGetRooms(currentUser.getBaseUrl()))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(roomsOverall -> { .subscribe(roomsOverall -> {
if (adapterWasNull) { if (adapterWasNull) {
@ -574,7 +567,7 @@ public class ConversationsListController extends BaseController implements Searc
@Override @Override
protected String getTitle() { public String getTitle() {
return getResources().getString(R.string.nc_app_name); return getResources().getString(R.string.nc_app_name);
} }

View File

@ -225,7 +225,7 @@ public class RingtoneSelectionController extends BaseController implements Flexi
} }
@Override @Override
protected String getTitle() { public String getTitle() {
return getResources().getString(R.string.nc_settings_notification_sounds); return getResources().getString(R.string.nc_settings_notification_sounds);
} }

View File

@ -243,7 +243,7 @@ public class ServerSelectionController extends BaseController {
ncApi.getServerStatus(queryUrl) ncApi.getServerStatus(queryUrl)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(status -> { .subscribe(status -> {
String productName = getResources().getString(R.string.nc_server_product_name); String productName = getResources().getString(R.string.nc_server_product_name);

View File

@ -510,7 +510,7 @@ public class SettingsController extends BaseController {
ApiUtils.getUrlForUserProfile(currentUser.getBaseUrl())) ApiUtils.getUrlForUserProfile(currentUser.getBaseUrl()))
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(userProfileOverall -> { .subscribe(userProfileOverall -> {
String displayName = null; String displayName = null;
@ -533,7 +533,7 @@ public class SettingsController extends BaseController {
null, currentUser.getId(), null, null, null) null, currentUser.getId(), null, null, null)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(userEntityResult -> { .subscribe(userEntityResult -> {
displayNameTextView.setText(userEntityResult.getDisplayName()); displayNameTextView.setText(userEntityResult.getDisplayName());
}, },
@ -667,7 +667,7 @@ public class SettingsController extends BaseController {
} }
@Override @Override
protected String getTitle() { public String getTitle() {
return getResources().getString(R.string.nc_settings); return getResources().getString(R.string.nc_settings);
} }

View File

@ -97,7 +97,7 @@ public class SwitchAccountController extends BaseController {
userUtils.createOrUpdateUser(null, userUtils.createOrUpdateUser(null,
null, null, null, null, null, null,
null, true, null, userEntity.getId(), null, null, null) null, true, null, userEntity.getId(), null, null, null)
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<UserEntity>() { .subscribe(new Observer<UserEntity>() {
@Override @Override
public void onSubscribe(Disposable d) { public void onSubscribe(Disposable d) {
@ -243,7 +243,7 @@ public class SwitchAccountController extends BaseController {
} }
@Override @Override
protected String getTitle() { public String getTitle() {
return getResources().getString(R.string.nc_select_an_account); return getResources().getString(R.string.nc_select_an_account);
} }
} }

View File

@ -392,7 +392,7 @@ public class WebViewLoginController extends BaseController {
null, currentUser.getId(), null, appPreferences.getTemporaryClientCertAlias(), null) null, currentUser.getId(), null, appPreferences.getTemporaryClientCertAlias(), null)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider)) .as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(userEntity -> { .subscribe(userEntity -> {
if (finalMessageType != null) { if (finalMessageType != null) {
ApplicationWideMessageHolder.getInstance().setMessageType(finalMessageType); ApplicationWideMessageHolder.getInstance().setMessageType(finalMessageType);

View File

@ -1,166 +0,0 @@
/**
* Nextcloud Talk application
*
* @author BlueLine Labs, Inc.
* Copyright (C) 2016 BlueLine Labs, Inc.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.nextcloud.talk.controllers.base;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.ActionBar;
import autodagger.AutoInjector;
import com.bluelinelabs.conductor.Controller;
import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider;
import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.controllers.AccountVerificationController;
import com.nextcloud.talk.controllers.ServerSelectionController;
import com.nextcloud.talk.controllers.SwitchAccountController;
import com.nextcloud.talk.controllers.WebViewLoginController;
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider;
import com.nextcloud.talk.utils.preferences.AppPreferences;
import com.uber.autodispose.lifecycle.LifecycleScopeProvider;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
@AutoInjector(NextcloudTalkApplication.class)
public abstract class BaseController extends ButterKnifeController {
private static final String TAG = "BaseController";
public final LifecycleScopeProvider scopeProvider = ControllerScopeProvider.from(this);
@Inject
AppPreferences appPreferences;
@Inject
Context context;
@Override
protected void onContextAvailable(Context context) {
super.onContextAvailable(context);
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
cleanTempCertPreference();
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
getRouter().popCurrentController();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void cleanTempCertPreference() {
List<String> temporaryClassNames = new ArrayList<>();
temporaryClassNames.add(ServerSelectionController.class.getName());
temporaryClassNames.add(AccountVerificationController.class.getName());
temporaryClassNames.add(WebViewLoginController.class.getName());
temporaryClassNames.add(SwitchAccountController.class.getName());
if (!temporaryClassNames.contains(getClass().getName())) {
appPreferences.removeTemporaryClientCertAlias();
}
}
@Override
protected void onViewBound(@NonNull View view) {
super.onViewBound(view);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.getIsKeyboardIncognito()) {
disableKeyboardPersonalisedLearning((ViewGroup) view);
}
}
// Note: This is just a quick demo of how an ActionBar *can* be accessed, not necessarily how it *should*
// be accessed. In a production app, this would use Dagger instead.
protected ActionBar getActionBar() {
ActionBarProvider actionBarProvider = null;
try {
actionBarProvider = ((ActionBarProvider) getActivity());
} catch (Exception exception) {
Log.d(TAG, "Failed to fetch the action bar provider");
}
return actionBarProvider != null ? actionBarProvider.getSupportActionBar() : null;
}
@Override
protected void onAttach(@NonNull View view) {
super.onAttach(view);
setTitle();
if (getActionBar() != null) {
getActionBar().setDisplayHomeAsUpEnabled(getParentController() != null || getRouter().getBackstackSize() > 1);
}
}
@Override
protected void onDetach(@NonNull View view) {
super.onDetach(view);
InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
protected void setTitle() {
Controller parentController = getParentController();
while (parentController != null) {
if (parentController instanceof BaseController && ((BaseController) parentController).getTitle() != null) {
return;
}
parentController = parentController.getParentController();
}
String title = getTitle();
ActionBar actionBar = getActionBar();
if (title != null && actionBar != null) {
actionBar.setTitle(title);
}
}
protected String getTitle() {
return null;
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void disableKeyboardPersonalisedLearning(final ViewGroup viewGroup) {
View view;
EditText editText;
for (int i = 0; i < viewGroup.getChildCount(); i++) {
view = viewGroup.getChildAt(i);
if (view instanceof EditText) {
editText = (EditText) view;
editText.setImeOptions(editText.getImeOptions() | EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING);
} else if (view instanceof ViewGroup) {
disableKeyboardPersonalisedLearning((ViewGroup) view);
}
}
}
}

View File

@ -0,0 +1,170 @@
/**
* Nextcloud Talk application
*
* @author BlueLine Labs, Inc.
* Copyright (C) 2016 BlueLine Labs, Inc.
*
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.nextcloud.talk.controllers.base
import android.content.Context
import android.os.Build
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import androidx.annotation.RequiresApi
import androidx.appcompat.app.ActionBar
import autodagger.AutoInjector
import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.controllers.AccountVerificationController
import com.nextcloud.talk.controllers.ServerSelectionController
import com.nextcloud.talk.controllers.SwitchAccountController
import com.nextcloud.talk.controllers.WebViewLoginController
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.uber.autodispose.lifecycle.LifecycleScopeProvider
import org.greenrobot.eventbus.EventBus
import org.koin.core.Koin
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.koin.core.qualifier.named
import org.koin.core.scope.Scope
import java.util.ArrayList
import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class)
abstract class BaseController : ButterKnifeController(), KoinComponent {
val scopeProvider: LifecycleScopeProvider<*> = ControllerScopeProvider.from(this)
val appPreferences: AppPreferences by inject()
val context: Context by inject()
val eventBus: EventBus by inject()
// Note: This is just a quick demo of how an ActionBar *can* be accessed, not necessarily how it *should*
// be accessed. In a production app, this would use Dagger instead.
protected val actionBar: ActionBar?
get() {
var actionBarProvider: ActionBarProvider? = null
try {
actionBarProvider = activity as ActionBarProvider?
} catch (exception: Exception) {
Log.d(TAG, "Failed to fetch the action bar provider")
}
return actionBarProvider?.supportActionBar
}
override fun getKoin(): Koin {
return super.getKoin()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> {
router.popCurrentController()
return true
}
else -> return super.onOptionsItemSelected(item)
}
}
private fun cleanTempCertPreference() {
val temporaryClassNames = ArrayList<String>()
temporaryClassNames.add(ServerSelectionController::class.java.name)
temporaryClassNames.add(AccountVerificationController::class.java.name)
temporaryClassNames.add(WebViewLoginController::class.java.name)
temporaryClassNames.add(SwitchAccountController::class.java.name)
if (!temporaryClassNames.contains(javaClass.name)) {
appPreferences.removeTemporaryClientCertAlias()
}
}
override fun onViewBound(view: View) {
super.onViewBound(view)
cleanTempCertPreference()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.isKeyboardIncognito) {
disableKeyboardPersonalisedLearning(view as ViewGroup)
}
}
override fun onAttach(view: View) {
super.onAttach(view)
eventBus.register(this)
setTitle()
if (actionBar != null) {
actionBar!!.setDisplayHomeAsUpEnabled(parentController != null || router.backstackSize > 1)
}
}
override fun onDetach(view: View) {
eventBus.unregister(this)
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(view.windowToken, 0)
super.onDetach(view)
}
protected fun setTitle() {
var parentController = parentController
while (parentController != null) {
if (parentController is BaseController && parentController.getTitle() != null) {
return
}
parentController = parentController.parentController
}
val title = getTitle()
val actionBar = actionBar
if (title != null && actionBar != null) {
actionBar.title = title
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private fun disableKeyboardPersonalisedLearning(viewGroup: ViewGroup) {
var view: View
var editText: EditText
for (i in 0 until viewGroup.childCount) {
view = viewGroup.getChildAt(i)
if (view is EditText) {
editText = view
editText.imeOptions = editText.imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
} else if (view is ViewGroup) {
disableKeyboardPersonalisedLearning(view)
}
}
}
companion object {
private val TAG = "BaseController"
}
open fun getTitle(): String? {
return null
}
}

View File

@ -43,6 +43,7 @@ import dagger.Provides;
import io.reactivex.schedulers.Schedulers; import io.reactivex.schedulers.Schedulers;
import java.io.IOException; import java.io.IOException;
import java.net.CookieManager; import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Proxy; import java.net.Proxy;
import java.security.KeyStore; import java.security.KeyStore;
@ -159,14 +160,15 @@ public class RestModule {
@Singleton @Singleton
@Provides @Provides
CookieManager provideCookieManager() { CookieManager provideCookieManager() {
return new CookieManager(); CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_NONE);
return cookieManager;
} }
@Singleton @Singleton
@Provides @Provides
Cache provideCache() { Cache provideCache() {
int cacheSize = 128 * 1024 * 1024; // 128 MB int cacheSize = 128 * 1024 * 1024; // 128 MB
return new Cache(NextcloudTalkApplication.Companion.getSharedApplication().getCacheDir(), cacheSize); return new Cache(NextcloudTalkApplication.Companion.getSharedApplication().getCacheDir(), cacheSize);
} }

View File

@ -0,0 +1,89 @@
/*
* 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/>.
*
* Inspired by: https://github.com/ZahraHeydari/Android-Clean-Arch-Coroutines-Koin/blob/c63e62bf8fb9c099a983fcc7d1b5616a7318aa3f/app/src/main/java/com/android/post/data/model/ErrorModel.kt
*/
package com.nextcloud.talk.newarch.data.model
/**
* This class designed to show different types of errors through error status & message
*
* */
private const val NO_CONNECTION_ERROR_MESSAGE = "No connection"
private const val BAD_RESPONSE_ERROR_MESSAGE = "Bad response"
private const val TIME_OUT_ERROR_MESSAGE = "Time out"
private const val EMPTY_RESPONSE_ERROR_MESSAGE = "Empty response"
private const val NOT_DEFINED_ERROR_MESSAGE = "Not defined"
private const val UNAUTHORIZED_ERROR_MESSAGE = "Unauthorized"
data class ErrorModel(
val message: String?,
val code: Int?,
var errorStatus: ErrorStatus
) {
constructor(errorStatus: ErrorStatus) : this(null, null, errorStatus)
constructor(
message: String?,
errorStatus: ErrorStatus
) : this(message, null, errorStatus)
fun getErrorMessage(): String {
return when (errorStatus) {
ErrorStatus.NO_CONNECTION -> NO_CONNECTION_ERROR_MESSAGE
ErrorStatus.BAD_RESPONSE -> BAD_RESPONSE_ERROR_MESSAGE
ErrorStatus.TIMEOUT -> TIME_OUT_ERROR_MESSAGE
ErrorStatus.EMPTY_RESPONSE -> EMPTY_RESPONSE_ERROR_MESSAGE
ErrorStatus.NOT_DEFINED -> NOT_DEFINED_ERROR_MESSAGE
ErrorStatus.UNAUTHORIZED -> UNAUTHORIZED_ERROR_MESSAGE
}
}
/**
* various error status to know what happened if something goes wrong with a repository call
*/
enum class ErrorStatus {
/**
* error in connecting to repository (Server or Database)
*/
NO_CONNECTION,
/**
* error in getting value (Json Error, Server Error, etc)
*/
BAD_RESPONSE,
/**
* Time out error
*/
TIMEOUT,
/**
* no data available in repository
*/
EMPTY_RESPONSE,
/**
* an unexpected error
*/
NOT_DEFINED,
/**
* bad credentials
*/
UNAUTHORIZED
}
}

View File

@ -0,0 +1,34 @@
/*
* 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.data.repository
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.newarch.data.source.remote.ApiService
import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository
import com.nextcloud.talk.utils.ApiUtils
class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : NextcloudTalkRepository {
override suspend fun getConversations(user: UserEntity): List<Conversation> {
return apiService.getConversations(ApiUtils.getCredentials(user.username, user.token),
ApiUtils.getUrlForGetRooms(user.baseUrl)).ocs.data
}
}

View File

@ -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/>.
*
* Inspired by https://github.com/ZahraHeydari/Android-Clean-Arch-Coroutines-Koin/blob
* /cfed12ecbfa425de53de505e76b3ec06c3bbdaca/app/src/main/java/com/android/post/data/source/remote/ApiErrorHandle.kt
*/
package com.nextcloud.talk.newarch.data.source.remote
import com.nextcloud.talk.newarch.data.model.ErrorModel
import okhttp3.ResponseBody
import retrofit2.HttpException
import java.io.IOException
import java.net.SocketTimeoutException
/**
* This class trace exceptions(api call or parse data or connection errors) &
* depending on what exception returns a [ErrorModel]
*
* */
class ApiErrorHandler {
fun traceErrorException(throwable: Throwable?): ErrorModel {
val errorModel: ErrorModel? = when (throwable) {
// if throwable is an instance of HttpException
// then attempt to parse error data from response body
is HttpException -> {
// handle UNAUTHORIZED situation (when token expired)
if (throwable.code() == 401) {
ErrorModel(throwable.message(), throwable.code(), ErrorModel.ErrorStatus.UNAUTHORIZED)
} else {
getHttpError(throwable.response()?.errorBody())
}
}
// handle api call timeout error
is SocketTimeoutException -> {
ErrorModel(throwable.message, ErrorModel.ErrorStatus.TIMEOUT)
}
// handle connection error
is IOException -> {
ErrorModel(throwable.message, ErrorModel.ErrorStatus.NO_CONNECTION)
}
else -> null
}
return errorModel ?: ErrorModel("No Defined Error!", 0, ErrorModel.ErrorStatus.BAD_RESPONSE)
}
/**
* attempts to parse http response body and get error data from it
*
* @param body retrofit response body
* @return returns an instance of [ErrorModel] with parsed data or NOT_DEFINED status
*/
private fun getHttpError(body: ResponseBody?): ErrorModel {
return try {
// use response body to get error detail
ErrorModel(body.toString(), 400, ErrorModel.ErrorStatus.BAD_RESPONSE)
} catch (e: Throwable) {
e.printStackTrace()
ErrorModel(message = e.message, errorStatus = ErrorModel.ErrorStatus.NOT_DEFINED)
}
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.data.source.remote
import com.nextcloud.talk.models.json.conversations.RoomsOverall
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Url
interface ApiService {
/*
Server URL is: baseUrl + ocsApiVersion + spreedApiVersion + /room
*/
@GET
suspend fun getConversations(
@Header(
"Authorization"
) authorization: String, @Url url: String
): RoomsOverall
}

View File

@ -0,0 +1,32 @@
/*
* 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.di.module
import org.greenrobot.eventbus.EventBus
import org.koin.dsl.module
val CommunicationModule = module {
single { createEventBus() }
}
fun createEventBus() : EventBus {
return EventBus.getDefault();
}

View File

@ -0,0 +1,250 @@
/*
* 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.di.module
import android.content.Context
import android.text.TextUtils
import android.util.Log
import com.github.aurae.retrofit2.LoganSquareConverterFactory
import com.nextcloud.talk.BuildConfig
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.newarch.data.repository.NextcloudTalkRepositoryImpl
import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
import com.nextcloud.talk.newarch.data.source.remote.ApiService
import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository
import com.nextcloud.talk.newarch.domain.usecases.GetConversationsUseCase
import com.nextcloud.talk.newarch.utils.NetworkUtils
import com.nextcloud.talk.newarch.utils.NetworkUtils.GetProxyRunnable
import com.nextcloud.talk.newarch.utils.NetworkUtils.MagicAuthenticator
import com.nextcloud.talk.utils.LoggingUtils
import com.nextcloud.talk.utils.database.user.UserUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.nextcloud.talk.utils.ssl.MagicKeyManager
import com.nextcloud.talk.utils.ssl.MagicTrustManager
import com.nextcloud.talk.utils.ssl.SSLSocketFactoryCompat
import io.reactivex.schedulers.Schedulers
import okhttp3.Cache
import okhttp3.Credentials
import okhttp3.Dispatcher
import okhttp3.JavaNetCookieJar
import okhttp3.OkHttpClient
import okhttp3.internal.tls.OkHostnameVerifier
import okhttp3.logging.HttpLoggingInterceptor
import okhttp3.logging.HttpLoggingInterceptor.Logger
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import java.io.IOException
import java.net.CookieManager
import java.net.CookiePolicy.ACCEPT_NONE
import java.net.Proxy
import java.security.KeyStore
import java.security.KeyStoreException
import java.security.NoSuchAlgorithmException
import java.security.UnrecoverableKeyException
import java.security.cert.CertificateException
import java.util.concurrent.TimeUnit
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.X509KeyManager
val NetworkModule = module {
single { createService(get()) }
single { createRetrofit(get()) }
single { createOkHttpClient(androidContext(), get(), get(), get(), get(), get(), get(), get()) }
factory { createGetConversationsUseCase(get(), get()) }
}
fun createCookieManager(): CookieManager {
val cookieManager = CookieManager()
cookieManager.setCookiePolicy(ACCEPT_NONE)
return cookieManager
}
fun createOkHttpClient(
context: Context,
proxy: Proxy,
appPreferences: AppPreferences,
magicTrustManager: MagicTrustManager,
sslSocketFactoryCompat: SSLSocketFactoryCompat,
cache: Cache,
cookieManager: CookieManager,
dispatcher: Dispatcher
): OkHttpClient {
val httpClient = OkHttpClient.Builder()
httpClient.retryOnConnectionFailure(true)
httpClient.connectTimeout(45, TimeUnit.SECONDS)
httpClient.readTimeout(45, TimeUnit.SECONDS)
httpClient.writeTimeout(45, TimeUnit.SECONDS)
httpClient.cookieJar(JavaNetCookieJar(cookieManager))
httpClient.cache(cache)
// Trust own CA and all self-signed certs
httpClient.sslSocketFactory(sslSocketFactoryCompat, magicTrustManager)
httpClient.retryOnConnectionFailure(true)
httpClient.hostnameVerifier(magicTrustManager.getHostnameVerifier(OkHostnameVerifier))
httpClient.dispatcher(dispatcher)
if (Proxy.NO_PROXY != proxy) {
httpClient.proxy(proxy)
if (appPreferences.proxyCredentials &&
!TextUtils.isEmpty(appPreferences.proxyUsername) &&
!TextUtils.isEmpty(appPreferences.proxyPassword)
) {
httpClient.proxyAuthenticator(
MagicAuthenticator(
Credentials.basic(
appPreferences.proxyUsername,
appPreferences.proxyPassword
), "Proxy-Authorization"
)
)
}
}
httpClient.addInterceptor(NetworkUtils.HeadersInterceptor())
if (BuildConfig.DEBUG && !context.getResources().getBoolean(R.bool.nc_is_debug)) {
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
loggingInterceptor.redactHeader("Authorization")
loggingInterceptor.redactHeader("Proxy-Authorization")
loggingInterceptor.redactHeader("Cookie")
httpClient.addInterceptor(loggingInterceptor)
} else if (context.getResources().getBoolean(R.bool.nc_is_debug)) {
val fileLogger = HttpLoggingInterceptor(object : Logger {
override fun log(message: String) {
LoggingUtils.writeLogEntryToFile(context, message)
}
})
fileLogger.level = HttpLoggingInterceptor.Level.BODY
fileLogger.redactHeader("Authorization")
fileLogger.redactHeader("Proxy-Authorization")
fileLogger.redactHeader("Cookie")
httpClient.addInterceptor(fileLogger)
}
return httpClient.build()
}
fun createRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://nextcloud.com")
.addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io()))
.addConverterFactory(LoganSquareConverterFactory.create())
.build()
}
fun createTrustManager(): MagicTrustManager {
return MagicTrustManager();
}
fun createSslSocketFactory(magicKeyManager: MagicKeyManager, magicTrustManager:
MagicTrustManager) : SSLSocketFactoryCompat {
return SSLSocketFactoryCompat(magicKeyManager, magicTrustManager)
}
fun createKeyManager(
appPreferences: AppPreferences,
userUtils: UserUtils
): MagicKeyManager? {
val keyStore: KeyStore?
try {
keyStore = KeyStore.getInstance("AndroidKeyStore")
keyStore.load(null)
val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
kmf.init(keyStore, null)
val origKm = kmf.keyManagers[0] as X509KeyManager
return MagicKeyManager(origKm, userUtils, appPreferences)
} catch (e: KeyStoreException) {
Log.e("NetworkModule", "KeyStoreException " + e.localizedMessage!!)
} catch (e: CertificateException) {
Log.e("NetworkModule", "CertificateException " + e.localizedMessage!!)
} catch (e: NoSuchAlgorithmException) {
Log.e("NetworkModule", "NoSuchAlgorithmException " + e.localizedMessage!!)
} catch (e: IOException) {
Log.e("NetworkModule", "IOException " + e.localizedMessage!!)
} catch (e: UnrecoverableKeyException) {
Log.e("NetworkModule", "UnrecoverableKeyException " + e.localizedMessage!!)
}
return null
}
fun createProxy(appPreferences: AppPreferences): Proxy {
if (!TextUtils.isEmpty(appPreferences.proxyType) && "No proxy" != appPreferences.proxyType
&& !TextUtils.isEmpty(appPreferences.proxyHost)
) {
val getProxyRunnable = GetProxyRunnable(appPreferences)
val getProxyThread = Thread(getProxyRunnable)
getProxyThread.start()
try {
getProxyThread.join()
return getProxyRunnable.proxyValue
} catch (e: InterruptedException) {
Log.e("NetworkModule", "Failed to join the thread while getting proxy: " + e.localizedMessage)
return Proxy.NO_PROXY
}
} else {
return Proxy.NO_PROXY
}
}
fun createDispatcher() : Dispatcher {
val dispatcher = Dispatcher()
dispatcher.maxRequestsPerHost = 100
dispatcher.maxRequests = 100
return dispatcher
}
fun createCache(androidApplication: NextcloudTalkApplication) : Cache {
val cacheSize = 128 * 1024 * 1024 // 128 MB
return Cache(androidApplication.cacheDir, cacheSize.toLong())
}
fun createApiErrorHandler(): ApiErrorHandler {
return ApiErrorHandler()
}
fun createService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
fun createNextcloudTalkRepository(apiService: ApiService): NextcloudTalkRepository {
return NextcloudTalkRepositoryImpl(apiService)
}
fun createGetConversationsUseCase(
nextcloudTalkRepository: NextcloudTalkRepository,
apiErrorHandler: ApiErrorHandler
): GetConversationsUseCase {
return GetConversationsUseCase(nextcloudTalkRepository, apiErrorHandler)
}

View File

@ -0,0 +1,62 @@
/*
* 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.di.module
import android.content.Context
import com.nextcloud.talk.R.string
import com.nextcloud.talk.models.database.Models
import com.nextcloud.talk.utils.database.user.UserUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import io.requery.Persistable
import io.requery.android.sqlcipher.SqlCipherDatabaseSource
import io.requery.reactivex.ReactiveEntityStore
import io.requery.reactivex.ReactiveSupport
import io.requery.sql.EntityDataStore
import net.orange_box.storebox.StoreBox
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
val StorageModule = module {
single { createPreferences(androidContext()) }
single { createSqlCipherDatabaseSource(androidContext()) }
single { createDataStore(get()) }
single { createUserUtils(get()) }
}
fun createPreferences(context: Context): AppPreferences {
return StoreBox.create<AppPreferences>(context, AppPreferences::class.java)
}
fun createSqlCipherDatabaseSource(context: Context): SqlCipherDatabaseSource {
return SqlCipherDatabaseSource(context, Models.DEFAULT,
context.resources.getString(string.nc_app_name).toLowerCase()
.replace(" ", "_").trim { it <= ' ' } + ".sqlite",
context.getString(string.nc_talk_database_encryption_key), 6)
}
fun createDataStore(sqlCipherDatabaseSource: SqlCipherDatabaseSource): ReactiveEntityStore<Persistable> {
val configuration = sqlCipherDatabaseSource.configuration
return ReactiveSupport.toReactiveStore(EntityDataStore(configuration))
}
fun createUserUtils(dataStore : ReactiveEntityStore<Persistable>) : UserUtils {
return UserUtils(dataStore);
}

View File

@ -0,0 +1,28 @@
/*
* 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.domain.repository
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.models.json.conversations.Conversation
interface NextcloudTalkRepository {
suspend fun getConversations(user: UserEntity): List<Conversation>
}

View File

@ -0,0 +1,36 @@
/*
* 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.domain.usecases
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
import com.nextcloud.talk.newarch.domain.repository.NextcloudTalkRepository
import com.nextcloud.talk.newarch.domain.usecases.base.UseCase
class GetConversationsUseCase constructor(
private val nextcloudTalkRepository: NextcloudTalkRepository,
apiErrorHandler: ApiErrorHandler?
) : UseCase<List<Conversation>, Any?>(apiErrorHandler) {
override suspend fun run(params: Any?): List<Conversation> {
return nextcloudTalkRepository.getConversations(user);
}
}

View File

@ -0,0 +1,51 @@
/*
* 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.domain.usecases.base
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import retrofit2.HttpException
abstract class UseCase<Type, in Params>(private val apiErrorHandler: ApiErrorHandler?) where Type : Any {
abstract suspend fun run(params: Params? = null): Type
lateinit var user: UserEntity
fun invoke(
scope: CoroutineScope,
params: Params?,
onResult: (UseCaseResponse<Type>)
) {
val backgroundJob = scope.async { run(params) }
scope.launch {
backgroundJob.await().let {
try {
onResult.onSuccess(it)
} catch (e: HttpException) {
onResult.onError(apiErrorHandler?.traceErrorException(e))
}
}
}
}
}

View File

@ -0,0 +1,29 @@
/*
* 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.domain.usecases.base
import com.nextcloud.talk.newarch.data.model.ErrorModel
interface UseCaseResponse<Type> {
fun onSuccess(result: Type)
fun onError(errorModel: ErrorModel?)
}

View File

@ -0,0 +1,118 @@
/*
* 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.utils
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.preferences.AppPreferences
import com.nextcloud.talk.utils.singletons.AvatarStatusCodeHolder
import okhttp3.Authenticator
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import okhttp3.Route
import java.io.IOException
import java.net.InetSocketAddress
import java.net.Proxy
import java.net.Proxy.NO_PROXY
import java.net.Proxy.Type
import java.net.Proxy.Type.SOCKS
class NetworkUtils {
class HeadersInterceptor : Interceptor {
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val original = chain.request()
val request = original.newBuilder()
.header("User-Agent", ApiUtils.getUserAgent())
.header("Accept", "application/json")
.header("OCS-APIRequest", "true")
.method(original.method, original.body)
.build()
val response = chain.proceed(request)
if (request.url.encodedPath.contains("/avatar/")) {
AvatarStatusCodeHolder.getInstance()
.statusCode = response.code
}
return response
}
}
class MagicAuthenticator(
private val credentials: String,
private val authenticatorType: String
) : Authenticator {
override fun authenticate(
route: Route?,
response: Response
): Request? {
if (response.request.header(authenticatorType) != null) {
return null
}
var countedResponse: Response? = response
var attemptsCount = 0
while ({ countedResponse = countedResponse?.priorResponse; countedResponse }() != null) {
attemptsCount++
if (attemptsCount == 3) {
return null
}
}
return response.request.newBuilder()
.header(authenticatorType, credentials)
.build()
}
}
class GetProxyRunnable internal constructor(private val appPreferences: AppPreferences) :
Runnable {
@Volatile internal var proxyValue: Proxy = NO_PROXY
private set
override fun run() {
if (SOCKS == Type.valueOf(appPreferences.proxyType)) {
proxyValue = Proxy(
Type.valueOf(appPreferences.proxyType),
InetSocketAddress.createUnresolved(
appPreferences.proxyHost, Integer.parseInt(
appPreferences.proxyPort
)
)
)
} else {
proxyValue = Proxy(
Type.valueOf(appPreferences.proxyType),
InetSocketAddress(
appPreferences.proxyHost,
Integer.parseInt(appPreferences.proxyPort)
)
)
}
}
}
}

View File

@ -36,7 +36,7 @@ import java.util.List;
public class UserUtils { public class UserUtils {
private ReactiveEntityStore<Persistable> dataStore; private ReactiveEntityStore<Persistable> dataStore;
UserUtils(ReactiveEntityStore<Persistable> dataStore) { public UserUtils(ReactiveEntityStore<Persistable> dataStore) {
this.dataStore = dataStore; this.dataStore = dataStore;
} }