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 {
workVersion = "1.0.1"
koin_version = "2.0.1"
}
@ -150,6 +151,17 @@ configurations.all {
dependencies {
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 'com.google.android.material:material:1.1.0-beta01'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2'

View File

@ -20,6 +20,7 @@
*/
package com.nextcloud.talk.application
import android.app.Application
import android.content.Context
import android.os.Build
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.PushRegistrationWorker
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.DeviceUtils
import com.nextcloud.talk.utils.DisplayUtils
@ -62,6 +66,9 @@ import de.cotech.hw.SecurityKeyManager
import de.cotech.hw.SecurityKeyManagerConfig
import okhttp3.OkHttpClient
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.voiceengine.WebRtcAudioManager
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])
@Singleton
@AutoInjector(NextcloudTalkApplication::class)
class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
class NextcloudTalkApplication : Application(), LifecycleObserver {
//region Fields (components)
lateinit var componentApplication: NextcloudTalkApplicationComponent
private set
@ -182,6 +189,12 @@ class NextcloudTalkApplication : MultiDexApplication(), LifecycleObserver {
.userModule(UserModule())
.arbitraryStorageModule(ArbitraryStorageModule())
.build()
startKoin {
androidContext(this@NextcloudTalkApplication)
androidLogger()
modules(listOf(CommunicationModule, StorageModule, NetworkModule))
}
}
override fun attachBaseContext(base: Context) {

View File

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

View File

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

View File

@ -192,8 +192,6 @@ public class CallController extends BaseController {
@Inject
NcApi ncApi;
@Inject
EventBus eventBus;
@Inject
UserUtils userUtils;
@Inject
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 {
@SuppressLint("ClickableViewAccessibility")

View File

@ -200,7 +200,7 @@ public class CallNotificationController extends BaseController {
.subscribeOn(Schedulers.io())
.takeWhile(observable -> !leavingScreen)
.retry(3)
.as(AutoDispose.autoDisposable(scopeProvider))
.as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<ParticipantsOverall>() {
@Override
public void onSubscribe(Disposable d) {
@ -249,7 +249,7 @@ public class CallNotificationController extends BaseController {
.subscribeOn(Schedulers.io())
.retry(3)
.observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider))
.as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<RoomsOverall>() {
@Override
public void onSubscribe(Disposable d) {
@ -390,19 +390,6 @@ public class CallNotificationController extends BaseController {
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() {
switch (currentConversation.getType()) {
case ROOM_TYPE_ONE_TO_ONE_CALL:

View File

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

View File

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

View File

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

View File

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

View File

@ -243,7 +243,7 @@ public class ServerSelectionController extends BaseController {
ncApi.getServerStatus(queryUrl)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider))
.as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(status -> {
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()))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider))
.as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(userProfileOverall -> {
String displayName = null;
@ -533,7 +533,7 @@ public class SettingsController extends BaseController {
null, currentUser.getId(), null, null, null)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider))
.as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(userEntityResult -> {
displayNameTextView.setText(userEntityResult.getDisplayName());
},
@ -667,7 +667,7 @@ public class SettingsController extends BaseController {
}
@Override
protected String getTitle() {
public String getTitle() {
return getResources().getString(R.string.nc_settings);
}

View File

@ -97,7 +97,7 @@ public class SwitchAccountController extends BaseController {
userUtils.createOrUpdateUser(null,
null, null, null,
null, true, null, userEntity.getId(), null, null, null)
.as(AutoDispose.autoDisposable(scopeProvider))
.as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(new Observer<UserEntity>() {
@Override
public void onSubscribe(Disposable d) {
@ -243,7 +243,7 @@ public class SwitchAccountController extends BaseController {
}
@Override
protected String getTitle() {
public String getTitle() {
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)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.as(AutoDispose.autoDisposable(scopeProvider))
.as(AutoDispose.autoDisposable(getScopeProvider()))
.subscribe(userEntity -> {
if (finalMessageType != null) {
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 java.io.IOException;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.security.KeyStore;
@ -159,14 +160,15 @@ public class RestModule {
@Singleton
@Provides
CookieManager provideCookieManager() {
return new CookieManager();
CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_NONE);
return cookieManager;
}
@Singleton
@Provides
Cache provideCache() {
int cacheSize = 128 * 1024 * 1024; // 128 MB
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 {
private ReactiveEntityStore<Persistable> dataStore;
UserUtils(ReactiveEntityStore<Persistable> dataStore) {
public UserUtils(ReactiveEntityStore<Persistable> dataStore) {
this.dataStore = dataStore;
}