diff --git a/ring-android/app/src/main/java/cx/ring/account/AccountCreationModelImpl.java b/ring-android/app/src/main/java/cx/ring/account/AccountCreationModelImpl.java deleted file mode 100644 index aa4173dd1f6887e984d2526f94d94cd24a4a7915..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/account/AccountCreationModelImpl.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.account; - -import android.graphics.Bitmap; - -import java.io.Serializable; - -import net.jami.mvp.AccountCreationModel; -import cx.ring.utils.BitmapUtils; -import ezvcard.VCard; -import ezvcard.property.FormattedName; -import ezvcard.property.Photo; -import ezvcard.property.RawProperty; -import ezvcard.property.Uid; -import io.reactivex.rxjava3.core.Single; - -public class AccountCreationModelImpl extends AccountCreationModel implements Serializable { - - @Override - public Single<VCard> toVCard() { - return Single - .fromCallable(() -> { - VCard vcard = new VCard(); - vcard.setFormattedName(new FormattedName(getFullName())); - vcard.setUid(new Uid(getUsername())); - Bitmap bmp = getPhoto(); - if (bmp != null) { - vcard.removeProperties(Photo.class); - vcard.addPhoto(BitmapUtils.bitmapToPhoto(bmp)); - } - vcard.removeProperties(RawProperty.class); - return vcard; - }); - } - - @Override - public Bitmap getPhoto() { - return (Bitmap) super.getPhoto(); - } - - public void setPhoto(Bitmap photo) { - super.setPhoto(photo); - } -} diff --git a/ring-android/app/src/main/java/cx/ring/account/AccountCreationModelImpl.kt b/ring-android/app/src/main/java/cx/ring/account/AccountCreationModelImpl.kt new file mode 100644 index 0000000000000000000000000000000000000000..e548adfa1000231414d62b2a1c8853f750a4bcde --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/account/AccountCreationModelImpl.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.account + +import net.jami.model.AccountCreationModel +import ezvcard.VCard +import ezvcard.property.FormattedName +import ezvcard.property.Uid +import android.graphics.Bitmap +import cx.ring.utils.BitmapUtils +import ezvcard.property.Photo +import ezvcard.property.RawProperty +import io.reactivex.rxjava3.core.Single +import java.io.Serializable + +class AccountCreationModelImpl : AccountCreationModel(), Serializable { + override fun toVCard(): Single<VCard> { + return Single.fromCallable { + val vcard = VCard() + vcard.formattedName = FormattedName(fullName) + vcard.uid = Uid(username) + val bmp = photo as Bitmap? + if (bmp != null) { + vcard.removeProperties(Photo::class.java) + vcard.addPhoto(BitmapUtils.bitmapToPhoto(bmp)) + } + vcard.removeProperties(RawProperty::class.java) + vcard + } + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/account/AccountEditionFragment.kt b/ring-android/app/src/main/java/cx/ring/account/AccountEditionFragment.kt index 19f84f4ea7e089e0048fab1cb4ccaa6b85ccf2da..d69e930e741ecf6eb5e3e6699db3859fd7da1ad3 100644 --- a/ring-android/app/src/main/java/cx/ring/account/AccountEditionFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/account/AccountEditionFragment.kt @@ -53,13 +53,8 @@ class AccountEditionFragment : BaseSupportFragment<AccountEditionPresenter, Acco private var mAccountId: String? = null private var mAccountIsJami = false - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - mBinding = FragAccountSettingsBinding.inflate(inflater, container, false) - return mBinding!!.root + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return FragAccountSettingsBinding.inflate(inflater, container, false).apply { mBinding = this }.root } override fun onDestroyView() { @@ -102,33 +97,32 @@ class AccountEditionFragment : BaseSupportFragment<AccountEditionPresenter, Acco } override fun initViewPager(accountId: String, isJami: Boolean) { - mBinding!!.pager.offscreenPageLimit = 4 - mBinding!!.slidingTabs.setupWithViewPager(mBinding!!.pager) - mBinding!!.pager.adapter = - PreferencesPagerAdapter(childFragmentManager, activity, accountId, isJami) - val existingFragment = - childFragmentManager.findFragmentByTag(BlockListFragment.TAG) as BlockListFragment? + mBinding?.apply { + pager.offscreenPageLimit = 4 + pager.adapter = PreferencesPagerAdapter(childFragmentManager, requireContext(), accountId, isJami) + slidingTabs.setupWithViewPager(pager) + } + val existingFragment = childFragmentManager.findFragmentByTag(BlockListFragment.TAG) as BlockListFragment? if (existingFragment != null) { - val args = Bundle() - args.putString(ACCOUNT_ID_KEY, accountId) - if (!existingFragment.isStateSaved) existingFragment.arguments = args + if (!existingFragment.isStateSaved) + existingFragment.arguments = Bundle().apply { putString(ACCOUNT_ID_KEY, accountId) } existingFragment.setAccount(accountId) } } override fun goToBlackList(accountId: String) { val blockListFragment = BlockListFragment() - val args = Bundle() - args.putString(ACCOUNT_ID_KEY, accountId) - blockListFragment.arguments = args + blockListFragment.arguments = Bundle().apply { putString(ACCOUNT_ID_KEY, accountId) } childFragmentManager.beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE) .addToBackStack(BlockListFragment.TAG) .replace(R.id.fragment_container, blockListFragment, BlockListFragment.TAG) .commit() - mBinding!!.slidingTabs.visibility = View.GONE - mBinding!!.pager.visibility = View.GONE - mBinding!!.fragmentContainer.visibility = View.VISIBLE + mBinding?.apply { + slidingTabs.visibility = View.GONE + pager.visibility = View.GONE + fragmentContainer.visibility = View.VISIBLE + } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { @@ -148,45 +142,45 @@ class AccountEditionFragment : BaseSupportFragment<AccountEditionPresenter, Acco override fun onBackPressed(): Boolean { if (mBinding == null) return false mIsVisible = false - if (activity is HomeActivity) (activity as HomeActivity?)!!.setToolbarOutlineState(true) + if (activity is HomeActivity) (activity as HomeActivity).setToolbarOutlineState(true) if (mBinding!!.fragmentContainer.visibility != View.VISIBLE) { toggleView(mAccountId, mAccountIsJami) return true } - val summaryFragment = - childFragmentManager.findFragmentByTag(JamiAccountSummaryFragment.TAG) as JamiAccountSummaryFragment? - return if (summaryFragment != null && summaryFragment.onBackPressed()) { + val summaryFragment = childFragmentManager.findFragmentByTag(JamiAccountSummaryFragment.TAG) as JamiAccountSummaryFragment? + return if (summaryFragment != null && summaryFragment.onBackPressed()) true - } else childFragmentManager.popBackStackImmediate() + else + childFragmentManager.popBackStackImmediate() } private fun toggleView(accountId: String?, isJami: Boolean) { mAccountId = accountId mAccountIsJami = isJami - mBinding!!.slidingTabs.visibility = if (isJami) View.GONE else View.VISIBLE - mBinding!!.pager.visibility = if (isJami) View.GONE else View.VISIBLE - mBinding!!.fragmentContainer.visibility = if (isJami) View.VISIBLE else View.GONE + mBinding?.apply { + slidingTabs.visibility = if (isJami) View.GONE else View.VISIBLE + pager.visibility = if (isJami) View.GONE else View.VISIBLE + fragmentContainer.visibility = if (isJami) View.VISIBLE else View.GONE + } setBackListenerEnabled(isJami) } override fun exit() { - val activity: Activity? = activity activity?.onBackPressed() } private fun setBackListenerEnabled(enable: Boolean) { val activity: Activity? = activity - if (activity is HomeActivity) activity.setAccountFragmentOnBackPressedListener(if (enable) this else null) + if (activity is HomeActivity) + activity.setAccountFragmentOnBackPressedListener(if (enable) this else null) } - private class PreferencesPagerAdapter internal constructor( - fm: FragmentManager?, - private val mContext: Context?, + private class PreferencesPagerAdapter constructor( + fm: FragmentManager, + private val mContext: Context, private val accountId: String, private val isJamiAccount: Boolean - ) : FragmentStatePagerAdapter( - fm!! - ) { + ) : FragmentStatePagerAdapter(fm) { override fun getCount(): Int { return if (isJamiAccount) 3 else 4 } @@ -195,10 +189,9 @@ class AccountEditionFragment : BaseSupportFragment<AccountEditionPresenter, Acco return if (isJamiAccount) getJamiPanel(position) else getSIPPanel(position) } - override fun getPageTitle(position: Int): CharSequence? { - val resId = - if (isJamiAccount) getRingPanelTitle(position) else getSIPPanelTitle(position) - return mContext!!.getString(resId) + override fun getPageTitle(position: Int): CharSequence { + val resId = if (isJamiAccount) getRingPanelTitle(position) else getSIPPanelTitle(position) + return mContext.getString(resId) } private fun getJamiPanel(position: Int): Fragment { @@ -221,10 +214,9 @@ class AccountEditionFragment : BaseSupportFragment<AccountEditionPresenter, Acco } private fun fragmentWithBundle(result: Fragment): Fragment { - val args = Bundle() - args.putString(ACCOUNT_ID_KEY, accountId) - result.arguments = args - return result + return result.apply { + arguments = Bundle().apply { putString(ACCOUNT_ID_KEY, accountId) } + } } companion object { @@ -256,20 +248,19 @@ class AccountEditionFragment : BaseSupportFragment<AccountEditionPresenter, Acco } private fun setupElevation() { - if (mBinding == null || !mIsVisible) { + val binding = mBinding ?: return + if (!mIsVisible) return - } val activity: FragmentActivity = activity as? HomeActivity ?: return - val ll = mBinding!!.pager.getChildAt(mBinding!!.pager.currentItem) as LinearLayout + val ll = binding.pager.getChildAt(binding.pager.currentItem) as LinearLayout val rv = (ll.getChildAt(0) as FrameLayout).getChildAt(0) as RecyclerView val homeActivity = activity as HomeActivity if (rv.canScrollVertically(SCROLL_DIRECTION_UP)) { - mBinding!!.slidingTabs.elevation = - mBinding!!.slidingTabs.resources.getDimension(R.dimen.toolbar_elevation) + binding.slidingTabs.elevation = binding.slidingTabs.resources.getDimension(R.dimen.toolbar_elevation) homeActivity.setToolbarElevation(true) homeActivity.setToolbarOutlineState(false) } else { - mBinding!!.slidingTabs.elevation = 0f + binding.slidingTabs.elevation = 0f homeActivity.setToolbarElevation(false) homeActivity.setToolbarOutlineState(true) } @@ -278,10 +269,9 @@ class AccountEditionFragment : BaseSupportFragment<AccountEditionPresenter, Acco companion object { private val TAG = AccountEditionFragment::class.simpleName @JvmField - val ACCOUNT_ID_KEY = AccountEditionFragment::class.qualifiedName + "accountid" + val ACCOUNT_ID_KEY = AccountEditionFragment::class.qualifiedName + "accountId" @JvmField - val ACCOUNT_HAS_PASSWORD_KEY = - AccountEditionFragment::class.qualifiedName + "hasPassword" + val ACCOUNT_HAS_PASSWORD_KEY = AccountEditionFragment::class.qualifiedName + "hasPassword" private const val SCROLL_DIRECTION_UP = -1 } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/account/AccountWizardActivity.kt b/ring-android/app/src/main/java/cx/ring/account/AccountWizardActivity.kt index 4beaf67e2fd199c22a44532570d42eed9c71c8a9..cefb308a1350f0010b150c906955171803cfd586 100644 --- a/ring-android/app/src/main/java/cx/ring/account/AccountWizardActivity.kt +++ b/ring-android/app/src/main/java/cx/ring/account/AccountWizardActivity.kt @@ -21,7 +21,6 @@ package cx.ring.account import android.app.ProgressDialog -import android.content.DialogInterface import android.content.Intent import android.content.pm.ActivityInfo import android.os.Bundle @@ -36,6 +35,7 @@ import cx.ring.client.HomeActivity import cx.ring.fragments.AccountMigrationFragment import cx.ring.fragments.SIPAccountCreationFragment import cx.ring.mvp.BaseActivity +import cx.ring.services.VCardServiceImpl import dagger.hilt.android.AndroidEntryPoint import ezvcard.VCard import io.reactivex.rxjava3.core.Single @@ -44,7 +44,7 @@ import net.jami.account.AccountWizardPresenter import net.jami.account.AccountWizardView import net.jami.model.Account import net.jami.model.AccountConfig -import net.jami.mvp.AccountCreationModel +import net.jami.model.AccountCreationModel import net.jami.utils.VCardUtils @AndroidEntryPoint @@ -71,29 +71,27 @@ class AccountWizardActivity : BaseActivity<AccountWizardPresenter>(), AccountWiz } if (savedInstanceState == null) { if (accountToMigrate != null) { - val args = Bundle() - args.putString(AccountMigrationFragment.ACCOUNT_ID, getIntent().data!!.lastPathSegment) - val fragment: Fragment = AccountMigrationFragment() - fragment.arguments = args - val fragmentManager = supportFragmentManager - fragmentManager + val fragment = AccountMigrationFragment().apply { + arguments = Bundle().apply { putString(AccountEditionFragment.ACCOUNT_ID_KEY, accountToMigrate) } + } + supportFragmentManager .beginTransaction() .replace(R.id.wizard_container, fragment) .commit() } else { - presenter.init(if (getIntent().action != null) getIntent().action else AccountConfig.ACCOUNT_TYPE_RING) + presenter.init(getIntent().action ?: AccountConfig.ACCOUNT_TYPE_RING) } } } override fun onDestroy() { - if (mProgress != null) { - mProgress!!.dismiss() + mProgress?.let { progress -> + progress.dismiss() mProgress = null } - if (mAlertDialog != null) { - mAlertDialog!!.setOnDismissListener(null) - mAlertDialog!!.dismiss() + mAlertDialog?.let { alertDialog -> + alertDialog.setOnDismissListener(null) + alertDialog.dismiss() mAlertDialog = null } super.onDestroy() @@ -103,7 +101,7 @@ class AccountWizardActivity : BaseActivity<AccountWizardPresenter>(), AccountWiz val filedir = filesDir return accountCreationModel.toVCard() .flatMap { vcard: VCard -> - account.resetProfile() + account.loadedProfile = Single.fromCallable { VCardServiceImpl.readData(vcard) }.cache() VCardUtils.saveLocalProfileToDisk(vcard, account.accountID, filedir) } .subscribeOn(Schedulers.io()) @@ -134,14 +132,14 @@ class AccountWizardActivity : BaseActivity<AccountWizardPresenter>(), AccountWiz .commit() } - override fun goToProfileCreation(model: AccountCreationModel) { + override fun goToProfileCreation(accountCreationModel: AccountCreationModel) { val fragments = supportFragmentManager.fragments if (fragments.size > 0) { val fragment = fragments[0] if (fragment is JamiLinkAccountFragment) { - fragment.scrollPagerFragment(model) + fragment.scrollPagerFragment(accountCreationModel) } else if (fragment is JamiAccountConnectFragment) { - profileCreated(model, false) + profileCreated(accountCreationModel, false) } } } @@ -217,7 +215,7 @@ class AccountWizardActivity : BaseActivity<AccountWizardPresenter>(), AccountWiz .setPositiveButton(android.R.string.ok, null) .setTitle(R.string.account_cannot_be_found_title) .setMessage(R.string.account_cannot_be_found_message) - .setOnDismissListener { dialogInterface: DialogInterface? -> supportFragmentManager.popBackStack() } + .setOnDismissListener { supportFragmentManager.popBackStack() } .show() } @@ -231,11 +229,11 @@ class AccountWizardActivity : BaseActivity<AccountWizardPresenter>(), AccountWiz presenter.successDialogClosed() } - fun profileCreated(accountCreationModel: AccountCreationModel?, saveProfile: Boolean) { + fun profileCreated(accountCreationModel: AccountCreationModel, saveProfile: Boolean) { presenter.profileCreated(accountCreationModel, saveProfile) } companion object { - val TAG = AccountWizardActivity::class.java.name + val TAG = AccountWizardActivity::class.simpleName!! } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiAccountConnectFragment.java b/ring-android/app/src/main/java/cx/ring/account/JamiAccountConnectFragment.java index ded0fab55521962897f542a4d3a6546ecc6c50f1..04b909ed31a7b2fa704e207b27a02baf4467b25d 100644 --- a/ring-android/app/src/main/java/cx/ring/account/JamiAccountConnectFragment.java +++ b/ring-android/app/src/main/java/cx/ring/account/JamiAccountConnectFragment.java @@ -31,12 +31,11 @@ import android.view.inputmethod.EditorInfo; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import cx.ring.application.JamiApplication; import cx.ring.databinding.FragAccJamiConnectBinding; import net.jami.account.JamiAccountConnectPresenter; import net.jami.account.JamiConnectAccountView; -import net.jami.mvp.AccountCreationModel; +import net.jami.model.AccountCreationModel; import cx.ring.mvp.BaseSupportFragment; import dagger.hilt.android.AndroidEntryPoint; diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiAccountCreationFragment.kt b/ring-android/app/src/main/java/cx/ring/account/JamiAccountCreationFragment.kt index 224be8c8cf93f723cde3e042a029c3f609937546..3492473254a8610de5f48420c908afb104006b70 100644 --- a/ring-android/app/src/main/java/cx/ring/account/JamiAccountCreationFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/account/JamiAccountCreationFragment.kt @@ -33,7 +33,7 @@ import androidx.fragment.app.FragmentStatePagerAdapter import androidx.viewpager.widget.ViewPager.OnPageChangeListener import cx.ring.databinding.FragAccJamiCreateBinding import cx.ring.views.WizardViewPager -import net.jami.mvp.AccountCreationModel +import net.jami.model.AccountCreationModel class JamiAccountCreationFragment : Fragment() { private var mBinding: FragAccJamiCreateBinding? = null @@ -42,7 +42,7 @@ class JamiAccountCreationFragment : Fragment() { override fun handleOnBackPressed() { if (mCurrentFragment is ProfileCreationFragment) { val fragment = mCurrentFragment as ProfileCreationFragment - (activity as AccountWizardActivity?)?.profileCreated(fragment.model, false) + (activity as AccountWizardActivity?)?.profileCreated(fragment.model!!, false) return } mBinding!!.pager.currentItem = mBinding!!.pager.currentItem - 1 diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiAccountPasswordFragment.java b/ring-android/app/src/main/java/cx/ring/account/JamiAccountPasswordFragment.java index d818e7d236500b008707fb1f2f4d295990b8d528..49ff65322e0d8924bdde7119421b3ee44eeb0648 100644 --- a/ring-android/app/src/main/java/cx/ring/account/JamiAccountPasswordFragment.java +++ b/ring-android/app/src/main/java/cx/ring/account/JamiAccountPasswordFragment.java @@ -38,7 +38,7 @@ import cx.ring.databinding.FragAccJamiPasswordBinding; import net.jami.account.JamiAccountCreationPresenter; import net.jami.account.JamiAccountCreationView; -import net.jami.mvp.AccountCreationModel; +import net.jami.model.AccountCreationModel; import cx.ring.mvp.BaseSupportFragment; import dagger.hilt.android.AndroidEntryPoint; diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt b/ring-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt index e610e253afb4a5c7f3fec1a3340066b85c22e439..2b3e544c0aa8b62b007f992153e28f2cce0a08c5 100644 --- a/ring-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/account/JamiAccountSummaryFragment.kt @@ -73,6 +73,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers import net.jami.account.JamiAccountSummaryPresenter import net.jami.account.JamiAccountSummaryView import net.jami.model.Account +import net.jami.model.Profile import net.jami.utils.StringUtils import java.io.File import java.util.* @@ -102,10 +103,8 @@ class JamiAccountSummaryFragment : private var tmpProfilePhotoUri: Uri? = null private var mDeviceAdapter: DeviceAdapter? = null private val mDisposableBag = CompositeDisposable() - private val mProfileDisposable = CompositeDisposable() private var mBinding: FragAccSummaryBinding? = null override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { - mDisposableBag.add(mProfileDisposable) return FragAccSummaryBinding.inflate(inflater, container, false).apply { mBinding = this }.root @@ -143,9 +142,8 @@ class JamiAccountSummaryFragment : items.add(SettingItem(R.string.account_preferences_advanced_tab, R.drawable.round_check_circle_24) { presenter.goToAdvanced() }) val adapter = SettingsAdapter(view.context, R.layout.item_setting, items) mBinding!!.settingsList.onItemClickListener = - AdapterView.OnItemClickListener { adapterView: AdapterView<*>?, v: View?, i: Int, l: Long -> - adapter.getItem(i)!! - .onClick() + AdapterView.OnItemClickListener { _, v: View?, i: Int, l: Long -> + adapter.getItem(i)?.onClick() } mBinding!!.settingsList.adapter = adapter var totalHeight = 0 @@ -185,39 +183,30 @@ class JamiAccountSummaryFragment : } } - fun setAccount(accountId: String?) { + fun setAccount(accountId: String) { presenter.setAccountId(accountId) } - override fun updateUserView(account: Account) { + override fun updateUserView(account: Account, profile: Profile) { val context = context ?: return - mProfileDisposable.clear() - mProfileDisposable.add(loadProfile(context, account) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ profile -> - mBinding?.let { binding -> - binding.userPhoto.setImageDrawable(AvatarDrawable.build(context, account, profile, true)) - binding.username.setText(profile.first) - } - }, { e: Throwable -> Log.e(TAG, "Error loading avatar", e) }) - ) + mBinding?.let { binding -> + binding.userPhoto.setImageDrawable(AvatarDrawable.build(context, account, profile, true)) + binding.username.setText(profile.displayName) + } } override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { when (requestCode) { WRITE_REQUEST_CODE -> if (resultCode == Activity.RESULT_OK) { - if (resultData != null) { - val uri = resultData.data - if (uri != null) { - if (mCacheArchive != null) { - AndroidFileUtils.moveToUri(requireContext().contentResolver, mCacheArchive!!, uri) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({}) { e: Throwable -> - val v = view - if (v != null) - Snackbar.make(v, "Can't export archive: " + e.message, Snackbar.LENGTH_LONG).show() - } - } + resultData?.data?.let { uri -> + mCacheArchive?.let { cacheArchive -> + AndroidFileUtils.moveToUri(requireContext().contentResolver, cacheArchive, uri) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({}) { e: Throwable -> + val v = view + if (v != null) + Snackbar.make(v, "Can't export archive: " + e.message, Snackbar.LENGTH_LONG).show() + } } } } @@ -226,7 +215,7 @@ class JamiAccountSummaryFragment : if (resultCode == Activity.RESULT_OK) { if (photoUri == null) { if (resultData != null) - updatePhoto(Single.just(resultData.extras!!["data"] as Bitmap?)) + updatePhoto(Single.just(resultData.extras!!["data"] as Bitmap)) } else { updatePhoto(photoUri) } @@ -240,8 +229,8 @@ class JamiAccountSummaryFragment : } } - override fun accountChanged(account: Account) { - updateUserView(account) + override fun accountChanged(account: Account, profile: Profile) { + updateUserView(account, profile) mBinding?.let { binding -> binding.userPhoto.setOnClickListener { profileContainerClicked(account) } binding.linkedDevices.setText(account.deviceName) @@ -255,7 +244,7 @@ class JamiAccountSummaryFragment : mBestName = "$mBestName.gz" val username = account.registeredName val currentRegisteredName = account.registeringUsername - val hasRegisteredName = !currentRegisteredName && username != null && !username.isEmpty() + val hasRegisteredName = !currentRegisteredName && username != null && username.isNotEmpty() binding.groupRegisteringName.visibility = if (currentRegisteredName) View.VISIBLE else View.GONE binding.btnShare.setOnClickListener { shareAccount(if (hasRegisteredName) username else account.username) } binding.registerName.visibility = if (hasRegisteredName) View.GONE else View.VISIBLE @@ -265,11 +254,11 @@ class JamiAccountSummaryFragment : .show(parentFragmentManager, QRCodeFragment.TAG) } binding.username.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus: Boolean -> - val name = binding.username.text - if (!hasFocus && !TextUtils.isEmpty(name)) { - presenter.saveVCardFormattedName(name.toString()) - } + val name = binding.username.text + if (!hasFocus) { + presenter.saveVCardFormattedName(name.toString()) } + } } setSwitchStatus(account) @@ -671,7 +660,7 @@ class JamiAccountSummaryFragment : }.show(parentFragmentManager, FRAGMENT_DIALOG_RENAME) } - override fun onDeviceRename(newName: String?) { + override fun onDeviceRename(newName: String) { Log.d(TAG, "onDeviceRename: " + presenter.deviceName + " -> " + newName) presenter.renameDevice(newName) } diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiAccountUsernameFragment.java b/ring-android/app/src/main/java/cx/ring/account/JamiAccountUsernameFragment.java index 5a50c8395a99c63f878c0d9248eb499e80414a61..4ce1a62717dfd64bb6836c855f5a6b7562a86e5f 100644 --- a/ring-android/app/src/main/java/cx/ring/account/JamiAccountUsernameFragment.java +++ b/ring-android/app/src/main/java/cx/ring/account/JamiAccountUsernameFragment.java @@ -43,7 +43,7 @@ import cx.ring.databinding.FragAccJamiUsernameBinding; import net.jami.account.JamiAccountCreationPresenter; import net.jami.account.JamiAccountCreationView; -import net.jami.mvp.AccountCreationModel; +import net.jami.model.AccountCreationModel; import cx.ring.mvp.BaseSupportFragment; import cx.ring.utils.RegisteredNameFilter; import dagger.hilt.android.AndroidEntryPoint; diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountFragment.kt b/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountFragment.kt index 34a6ccdbc798908215cf305745b7510483590c48..da7c68b27ce297b0aeee319c0e1c21536f809443 100644 --- a/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountFragment.kt @@ -31,7 +31,7 @@ import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentStatePagerAdapter import androidx.viewpager.widget.ViewPager.OnPageChangeListener import cx.ring.databinding.FragAccJamiLinkBinding -import net.jami.mvp.AccountCreationModel +import net.jami.model.AccountCreationModel class JamiLinkAccountFragment : Fragment() { private lateinit var model: AccountCreationModel @@ -43,7 +43,7 @@ class JamiLinkAccountFragment : Fragment() { override fun handleOnBackPressed() { if (mCurrentFragment is ProfileCreationFragment) { val fragment = mCurrentFragment as ProfileCreationFragment - (activity as AccountWizardActivity?)!!.profileCreated(fragment.model, false) + (activity as AccountWizardActivity?)!!.profileCreated(fragment.model!!, false) return } mBinding!!.pager.currentItem = mBinding!!.pager.currentItem - 1 diff --git a/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountPasswordFragment.java b/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountPasswordFragment.java index 81b175a7d0ef4a77ee78e1c5bf6b5cd90eef6354..78564a78a444f6366043b492d86d402f2a5487a0 100644 --- a/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountPasswordFragment.java +++ b/ring-android/app/src/main/java/cx/ring/account/JamiLinkAccountPasswordFragment.java @@ -34,12 +34,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import cx.ring.R; -import cx.ring.application.JamiApplication; import cx.ring.databinding.FragAccJamiLinkPasswordBinding; import net.jami.account.JamiLinkAccountPresenter; import net.jami.account.JamiLinkAccountView; -import net.jami.mvp.AccountCreationModel; +import net.jami.model.AccountCreationModel; import cx.ring.mvp.BaseSupportFragment; import dagger.hilt.android.AndroidEntryPoint; @@ -109,7 +108,7 @@ public class JamiLinkAccountPasswordFragment extends BaseSupportFragment<JamiLin } @Override - protected void initPresenter(net.jami.account.JamiLinkAccountPresenter presenter) { + protected void initPresenter(JamiLinkAccountPresenter presenter) { presenter.init(model); } diff --git a/ring-android/app/src/main/java/cx/ring/account/ProfileCreationFragment.kt b/ring-android/app/src/main/java/cx/ring/account/ProfileCreationFragment.kt index e568b8ca99dab626f7cd13e11e197f23f6e842e1..3358879f089e11fbbca6423039d69170b97d9bd3 100644 --- a/ring-android/app/src/main/java/cx/ring/account/ProfileCreationFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/account/ProfileCreationFragment.kt @@ -48,7 +48,7 @@ import dagger.hilt.android.AndroidEntryPoint import io.reactivex.rxjava3.core.Single import net.jami.account.ProfileCreationPresenter import net.jami.account.ProfileCreationView -import net.jami.mvp.AccountCreationModel +import net.jami.model.AccountCreationModel import java.io.IOException @AndroidEntryPoint @@ -87,7 +87,7 @@ class ProfileCreationFragment : BaseSupportFragment<ProfileCreationPresenter, Pr .build(view.context) ) } - presenter.initPresenter(model) + presenter.initPresenter(model!!) binding!!.gallery.setOnClickListener { presenter.galleryClick() } binding!!.camera.setOnClickListener { presenter.cameraClick() } binding!!.nextCreateAccount.setOnClickListener { presenter.nextClick() } @@ -197,11 +197,8 @@ class ProfileCreationFragment : BaseSupportFragment<ProfileCreationPresenter, Pr val newAccount = model.newAccount binding!!.profilePhoto.setImageDrawable( AvatarDrawable.Builder() - .withPhoto(model.photo) - .withNameData( - accountCreationModel.getFullName(), - accountCreationModel.getUsername() - ) + .withPhoto(model.photo as Bitmap?) + .withNameData(accountCreationModel.fullName, accountCreationModel.username) .withId(newAccount?.username) .withCircleCrop(true) .build(requireContext()) @@ -215,7 +212,7 @@ class ProfileCreationFragment : BaseSupportFragment<ProfileCreationPresenter, Pr const val REQUEST_PERMISSION_CAMERA = 3 const val REQUEST_PERMISSION_READ_STORAGE = 4 - fun newInstance(model: AccountCreationModelImpl?): ProfileCreationFragment { + fun newInstance(model: AccountCreationModelImpl): ProfileCreationFragment { val fragment = ProfileCreationFragment() fragment.model = model return fragment diff --git a/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.kt b/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.kt index 2eab16808c74e35fc92b9926164a5752fd6cda58..294d745464bb14b55cee0c8ad508cc0eb3c0d1ec 100644 --- a/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.kt +++ b/ring-android/app/src/main/java/cx/ring/account/RenameDeviceDialog.kt @@ -102,7 +102,7 @@ class RenameDeviceDialog : DialogFragment() { } interface RenameDeviceListener { - fun onDeviceRename(newName: String?) + fun onDeviceRename(newName: String) } companion object { diff --git a/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt b/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt index 6647eaef7e47b0d6c6c6af9ae636595f2db6017c..ebd010d97454edd877144898e981504e678eee07 100644 --- a/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt +++ b/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt @@ -239,7 +239,6 @@ class ConversationAdapter( out } Interaction.InteractionType.INVALID -> MessageType.INVALID.ordinal - null -> MessageType.INVALID.ordinal } } @@ -258,7 +257,7 @@ class ConversationAdapter( val interaction = mInteractions[position] conversationViewHolder.compositeDisposable.clear() if (position > lastMsgPos) { - lastMsgPos = position + //lastMsgPos = position val animation = AnimationUtils.loadAnimation(conversationViewHolder.itemView.context, R.anim.fade_in) animation.startOffset = 150 conversationViewHolder.itemView.startAnimation(animation) @@ -400,7 +399,7 @@ class ConversationAdapter( val options = ActivityOptionsCompat.makeSceneTransitionAnimation(conversationFragment.requireActivity(), viewHolder.mImage, "picture") conversationFragment.startActivityForResult(i, 3006, options.toBundle()) } catch (e: Exception) { - Log.w(TAG, "Can't open picture", e); + Log.w(TAG, "Can't open picture", e) } } } @@ -535,23 +534,15 @@ class ConversationAdapter( val timeString = timestampToDetailString(viewHolder.itemView.context, file.timestamp) viewHolder.compositeDisposable.add(timestampUpdateTimer.subscribe { when (val status = file.status) { - InteractionStatus.TRANSFER_FINISHED -> { - viewHolder.mMsgDetailTxt.text = String.format("%s - %s", timeString, - Formatter.formatFileSize(viewHolder.itemView.context, file.totalSize)) - } - InteractionStatus.TRANSFER_ONGOING -> { - viewHolder.mMsgDetailTxt.text = String.format("%s / %s - %s", - Formatter.formatFileSize(viewHolder.itemView.context, file.bytesProgress), - Formatter.formatFileSize(viewHolder.itemView.context, file.totalSize), - ResourceMapper.getReadableFileTransferStatus(viewHolder.itemView.context, status) - ) - } - else -> { - viewHolder.mMsgDetailTxt.text = String.format("%s - %s - %s", timeString, - Formatter.formatFileSize(viewHolder.itemView.context, file.totalSize), - ResourceMapper.getReadableFileTransferStatus(viewHolder.itemView.context, status) - ) - } + InteractionStatus.TRANSFER_FINISHED -> viewHolder.mMsgDetailTxt.text = String.format("%s - %s", timeString, + Formatter.formatFileSize(viewHolder.itemView.context, file.totalSize)) + InteractionStatus.TRANSFER_ONGOING -> viewHolder.mMsgDetailTxt.text = String.format("%s / %s - %s", + Formatter.formatFileSize(viewHolder.itemView.context, file.bytesProgress), + Formatter.formatFileSize(viewHolder.itemView.context, file.totalSize), + ResourceMapper.getReadableFileTransferStatus(viewHolder.itemView.context, status)) + else -> viewHolder.mMsgDetailTxt.text = String.format("%s - %s - %s", timeString, + Formatter.formatFileSize(viewHolder.itemView.context, file.totalSize), + ResourceMapper.getReadableFileTransferStatus(viewHolder.itemView.context, status)) } }) val type = viewHolder.type.transferType @@ -855,10 +846,7 @@ class ConversationAdapter( longPressView.setOnLongClickListener { v: View -> longPressView.background.setTint(conversationFragment.resources.getColor(R.color.grey_500)) conversationFragment.updatePosition(convViewHolder.adapterPosition) - mCurrentLongItem = RecyclerViewContextMenuInfo( - convViewHolder.adapterPosition, v.id - .toLong() - ) + mCurrentLongItem = RecyclerViewContextMenuInfo(convViewHolder.adapterPosition, v.id.toLong()) false } val pictureResID: Int @@ -903,25 +891,19 @@ class ConversationAdapter( if (diff < DateUtils.DAY_IN_MILLIS && DateUtils.isToday(timestamp)) { // 11:32 A.M. DateUtils.formatDateTime(context, timestamp, DateUtils.FORMAT_SHOW_TIME) } else { - DateUtils.formatDateTime( - context, timestamp, + DateUtils.formatDateTime(context, timestamp, DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_NO_YEAR or - DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_TIME - ) + DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_TIME) } } else if (diff < DateUtils.YEAR_IN_MILLIS) { // JAN. 7, 11:02 A.M. - DateUtils.formatDateTime( - context, timestamp, + DateUtils.formatDateTime(context, timestamp, DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_NO_YEAR or - DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_TIME - ) + DateUtils.FORMAT_ABBREV_ALL or DateUtils.FORMAT_SHOW_TIME) } else { - DateUtils.formatDateTime( - context, timestamp, + DateUtils.formatDateTime(context, timestamp, DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_YEAR or DateUtils.FORMAT_SHOW_WEEKDAY or - DateUtils.FORMAT_ABBREV_ALL - ) + DateUtils.FORMAT_ABBREV_ALL) } return timeStr.uppercase(Locale.getDefault()) } @@ -990,11 +972,7 @@ class ConversationAdapter( val nextMsg = getNextMessageFromPosition(i) if (prevMsg != null && nextMsg != null) { val nextMsgHasTime = hasPermanentTimeString(nextMsg, i + 1) - if ((isSeqBreak(msg, prevMsg) || isTimeShown) && !(isSeqBreak( - msg, - nextMsg - ) || nextMsgHasTime) - ) { + if ((isSeqBreak(msg, prevMsg) || isTimeShown) && !(isSeqBreak(msg, nextMsg) || nextMsgHasTime)) { return SequenceType.FIRST } else if (!isSeqBreak(msg, prevMsg) && !isTimeShown && isSeqBreak(msg, nextMsg)) { return SequenceType.LAST @@ -1048,16 +1026,14 @@ class ConversationAdapter( return false } val prevMsg = getPreviousMessageFromPosition(position) - return prevMsg != null && - msg.timestamp - prevMsg.timestamp > 10 * DateUtils.MINUTE_IN_MILLIS + return prevMsg != null && msg.timestamp - prevMsg.timestamp > 10 * DateUtils.MINUTE_IN_MILLIS } private fun lastOutgoingIndex(): Int { var i: Int = mInteractions.size - 1 while (i >= 0) { - if (!mInteractions[i].isIncoming) { + if (!mInteractions[i].isIncoming) break - } i-- } return i @@ -1123,19 +1099,14 @@ class ConversationAdapter( val res = conversationFragment.resources hPadding = res.getDimensionPixelSize(R.dimen.padding_medium) vPadding = res.getDimensionPixelSize(R.dimen.padding_small) - mPictureMaxSize = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - 200f, - res.displayMetrics - ).toInt() + mPictureMaxSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200f, res.displayMetrics).toInt() val corner = res.getDimension(R.dimen.conversation_message_radius).toInt() PICTURE_OPTIONS = GlideOptions() .transform(CenterInside()) .fitCenter() .override(mPictureMaxSize) .transform(RoundedCorners(corner)) - timestampUpdateTimer = - Observable.interval(10, TimeUnit.SECONDS, AndroidSchedulers.mainThread()) - .startWithItem(0L) + timestampUpdateTimer = Observable.interval(10, TimeUnit.SECONDS, AndroidSchedulers.mainThread()) + .startWithItem(0L) } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/adapters/RingtoneAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/RingtoneAdapter.java deleted file mode 100644 index a22acb9c60462e0a30079a3b943661ac3e8a591c..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/adapters/RingtoneAdapter.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Authors: Rayan Osseiran <rayan.osseiran@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.adapters; - -import android.graphics.drawable.Drawable; -import android.media.MediaPlayer; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.request.target.DrawableImageViewTarget; - -import java.io.IOException; -import java.util.List; - -import cx.ring.R; -import io.reactivex.rxjava3.core.Observable; -import io.reactivex.rxjava3.subjects.PublishSubject; -import io.reactivex.rxjava3.subjects.Subject; - -import net.jami.model.Ringtone; -import net.jami.utils.Log; - -public class RingtoneAdapter extends RecyclerView.Adapter<RingtoneAdapter.RingtoneViewHolder> { - - private final String TAG = RingtoneAdapter.class.getSimpleName(); - private final List<Ringtone> ringtoneList; - private int currentlySelectedPosition = 1; // default item - private final MediaPlayer mp = new MediaPlayer(); - private final Subject<Ringtone> ringtoneSubject = PublishSubject.create(); - - static class RingtoneViewHolder extends RecyclerView.ViewHolder { - private final TextView name; - private final ImageView isSelected, isPlaying, ringtoneIcon; - - RingtoneViewHolder(View view) { - super(view); - name = view.findViewById(R.id.item_ringtone_name); - isSelected = view.findViewById(R.id.item_ringtone_selected); - isPlaying = view.findViewById(R.id.item_ringtone_playing); - ringtoneIcon = view.findViewById(R.id.item_ringtone_icon); - Glide.with(view.getContext()) - .load(R.raw.baseline_graphic_eq_black_24dp) - .placeholder(R.drawable.baseline_graphic_eq_24) - .into(new DrawableImageViewTarget(isPlaying)); - } - } - - - public RingtoneAdapter(List<Ringtone> ringtones) { - ringtoneList = ringtones; - } - - @Override - @NonNull - public RingtoneViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - RingtoneViewHolder viewHolder = new RingtoneViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_ringtone, parent, false)); - configureRingtoneView(viewHolder); - return viewHolder; - } - - @Override - public void onBindViewHolder(@NonNull RingtoneViewHolder holder, int position) { - final Ringtone ringtone = ringtoneList.get(position); - holder.name.setText(ringtone.getName()); - holder.ringtoneIcon.setImageDrawable((Drawable) ringtone.getRingtoneIcon()); - holder.isSelected.setVisibility((ringtone.isSelected() ? View.VISIBLE : View.INVISIBLE)); - holder.isPlaying.setVisibility((ringtone.isPlaying() ? View.VISIBLE : View.INVISIBLE)); - } - - @Override - public int getItemCount() { - return ringtoneList.size(); - } - - private void configureRingtoneView(RingtoneViewHolder viewHolder) { - viewHolder.itemView.setOnClickListener(view -> { - if (currentlySelectedPosition == viewHolder.getAdapterPosition() && mp.isPlaying()) { - stopPreview(); - return; - } else { - resetState(); - } - - currentlySelectedPosition = viewHolder.getAdapterPosition(); - Ringtone ringtone = ringtoneList.get(currentlySelectedPosition); - try { - mp.setDataSource(ringtone.getRingtonePath()); - mp.prepare(); - mp.start(); - ringtone.setPlaying(true); - } catch (IOException | IllegalStateException | NullPointerException e) { - stopPreview(); - Log.e(TAG, "Error previewing ringtone", e); - } finally { - ringtoneSubject.onNext(ringtone); - ringtone.setSelected(true); - notifyItemChanged(currentlySelectedPosition); - } - mp.setOnCompletionListener(mp -> - stopPreview()); - }); - } - - /** - * Stops the preview from playing and disables the playing animation - */ - public void stopPreview() { - if(mp.isPlaying()) - mp.stop(); - mp.reset(); - ringtoneList.get(currentlySelectedPosition).setPlaying(false); - notifyItemChanged(currentlySelectedPosition); - } - - public void releaseMediaPlayer() { - mp.release(); - } - - /** - * Deselects the current item and stops the preview - */ - public void resetState() { - ringtoneList.get(currentlySelectedPosition).setSelected(false); - stopPreview(); - } - - /** - * Sets the ringtone from the user settings - * @param path the ringtone path - * @param enabled true if the user did not select silent - */ - public void selectDefaultItem(String path, boolean enabled) { - if (!enabled) { - currentlySelectedPosition = 0; - } else { - // ignore first element because it represents silent and has a null path (checked for before) - for (int ringtoneIndex = 1; ringtoneIndex < ringtoneList.size(); ringtoneIndex++) { - if (ringtoneList.get(ringtoneIndex).getRingtonePath().equals(path)) { - currentlySelectedPosition = ringtoneIndex; - } - } - } - ringtoneList.get(currentlySelectedPosition).setSelected(true); - notifyItemChanged(currentlySelectedPosition); - } - - public Observable<Ringtone> getRingtoneSubject() { - return ringtoneSubject; - } - -} diff --git a/ring-android/app/src/main/java/cx/ring/adapters/RingtoneAdapter.kt b/ring-android/app/src/main/java/cx/ring/adapters/RingtoneAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..b02092568fb8d47bd7ab701aeee943e327aef6da --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/adapters/RingtoneAdapter.kt @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Authors: Rayan Osseiran <rayan.osseiran@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.adapters + +import android.graphics.drawable.Drawable +import android.media.MediaPlayer +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.request.target.DrawableImageViewTarget +import cx.ring.R +import cx.ring.adapters.RingtoneAdapter.RingtoneViewHolder +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.subjects.PublishSubject +import io.reactivex.rxjava3.subjects.Subject +import net.jami.model.Ringtone +import java.io.IOException + +class RingtoneAdapter(private val ringtoneList: List<Ringtone>) : RecyclerView.Adapter<RingtoneViewHolder>() { + private var currentlySelectedPosition = 1 // default item + private val mp = MediaPlayer() + private val ringtoneSubject: Subject<Ringtone> = PublishSubject.create() + + class RingtoneViewHolder(view: View) : RecyclerView.ViewHolder(view) { + val name: TextView = view.findViewById(R.id.item_ringtone_name) + val isSelected: ImageView = view.findViewById(R.id.item_ringtone_selected) + val isPlaying: ImageView = view.findViewById(R.id.item_ringtone_playing) + val ringtoneIcon: ImageView = view.findViewById(R.id.item_ringtone_icon) + + init { + Glide.with(view.context) + .load(R.raw.baseline_graphic_eq_black_24dp) + .placeholder(R.drawable.baseline_graphic_eq_24) + .into(DrawableImageViewTarget(isPlaying)) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RingtoneViewHolder { + val viewHolder = RingtoneViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_ringtone, parent, false)) + configureRingtoneView(viewHolder) + return viewHolder + } + + override fun onBindViewHolder(holder: RingtoneViewHolder, position: Int) { + val ringtone = ringtoneList[position] + holder.name.text = ringtone.name + holder.ringtoneIcon.setImageDrawable(ringtone.ringtoneIcon as Drawable) + holder.isSelected.visibility = if (ringtone.isSelected) View.VISIBLE else View.INVISIBLE + holder.isPlaying.visibility = if (ringtone.isPlaying) View.VISIBLE else View.INVISIBLE + } + + override fun getItemCount(): Int { + return ringtoneList.size + } + + private fun configureRingtoneView(viewHolder: RingtoneViewHolder) { + viewHolder.itemView.setOnClickListener { + if (currentlySelectedPosition == viewHolder.adapterPosition && mp.isPlaying) { + stopPreview() + return@setOnClickListener + } else { + resetState() + } + currentlySelectedPosition = viewHolder.adapterPosition + val ringtone = ringtoneList[currentlySelectedPosition] + try { + mp.setDataSource(ringtone.ringtonePath) + mp.prepare() + mp.start() + ringtone.isPlaying = true + } catch (e: IOException) { + stopPreview() + Log.e(TAG, "Error previewing ringtone", e) + } catch (e: IllegalStateException) { + stopPreview() + Log.e(TAG, "Error previewing ringtone", e) + } catch (e: NullPointerException) { + stopPreview() + Log.e(TAG, "Error previewing ringtone", e) + } finally { + ringtoneSubject.onNext(ringtone) + ringtone.isSelected = true + notifyItemChanged(currentlySelectedPosition) + } + mp.setOnCompletionListener { stopPreview() } + } + } + + /** + * Stops the preview from playing and disables the playing animation + */ + private fun stopPreview() { + if (mp.isPlaying) mp.stop() + mp.reset() + ringtoneList[currentlySelectedPosition].isPlaying = false + notifyItemChanged(currentlySelectedPosition) + } + + fun releaseMediaPlayer() { + mp.release() + } + + /** + * Deselects the current item and stops the preview + */ + fun resetState() { + ringtoneList[currentlySelectedPosition].isSelected = false + stopPreview() + } + + /** + * Sets the ringtone from the user settings + * @param path the ringtone path + * @param enabled true if the user did not select silent + */ + fun selectDefaultItem(path: String, enabled: Boolean) { + if (!enabled) { + currentlySelectedPosition = 0 + } else { + // ignore first element because it represents silent and has a null path (checked for before) + for (ringtoneIndex in 1 until ringtoneList.size) { + if (ringtoneList[ringtoneIndex].ringtonePath == path) { + currentlySelectedPosition = ringtoneIndex + } + } + } + ringtoneList[currentlySelectedPosition].isSelected = true + notifyItemChanged(currentlySelectedPosition) + } + + fun getRingtone(): Observable<Ringtone> { + return ringtoneSubject + } + + companion object { + private val TAG = RingtoneAdapter::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/client/AccountSpinnerAdapter.kt b/ring-android/app/src/main/java/cx/ring/client/AccountSpinnerAdapter.kt index 4565d7ed3ccd0b11805446d51b72e450a6ee9aca..28b16a428817b288242488d046648d570a9ac191 100644 --- a/ring-android/app/src/main/java/cx/ring/client/AccountSpinnerAdapter.kt +++ b/ring-android/app/src/main/java/cx/ring/client/AccountSpinnerAdapter.kt @@ -30,11 +30,12 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import android.widget.RelativeLayout import cx.ring.databinding.ItemToolbarSelectedBinding import cx.ring.databinding.ItemToolbarSpinnerBinding +import cx.ring.services.VCardServiceImpl import io.reactivex.rxjava3.disposables.CompositeDisposable -import io.reactivex.rxjava3.schedulers.Schedulers import net.jami.model.Account +import net.jami.model.Profile -class AccountSpinnerAdapter(context: Context, accounts: List<Account>) : +class AccountSpinnerAdapter(context: Context, accounts: List<Account>, val disposable: CompositeDisposable) : ArrayAdapter<Account>(context, R.layout.item_toolbar_spinner, accounts) { private val mInflater: LayoutInflater = LayoutInflater.from(context) private val logoSize: Int = context.resources.getDimensionPixelSize(R.dimen.list_medium_icon_size) @@ -44,7 +45,7 @@ class AccountSpinnerAdapter(context: Context, accounts: List<Account>) : val type = getItemViewType(position) val holder: ViewHolderHeader if (view == null) { - holder = ViewHolderHeader(ItemToolbarSelectedBinding.inflate(mInflater, parent, false)) + holder = ViewHolderHeader(ItemToolbarSelectedBinding.inflate(mInflater, parent, false), disposable) view = holder.binding.root view.setTag(holder) } else { @@ -53,14 +54,13 @@ class AccountSpinnerAdapter(context: Context, accounts: List<Account>) : } if (type == TYPE_ACCOUNT) { val account = getItem(position)!! - holder.loader.add(AvatarDrawable.load(context, account) + holder.loader.add(VCardServiceImpl.loadProfile(context, account) + .map { profile -> Profile(profile.displayName ?: account.alias, AvatarDrawable.build(context, account, profile)) } .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ avatar -> holder.binding.logo.setImageDrawable(avatar) - }) { e: Throwable -> Log.e(TAG, "Error loading avatar", e) }) - holder.loader.add(account.accountAlias - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ alias -> holder.binding.title.text = alias.ifEmpty { context.getString(R.string.ring_account) } - }) { e: Throwable -> Log.e(TAG, "Error loading title", e) }) + .subscribe({ profile -> + holder.binding.logo.setImageDrawable(profile.avatar as AvatarDrawable) + holder.binding.title.text = profile.displayName?.ifEmpty { context.getString(R.string.ring_account) } + }){ e: Throwable -> Log.e(TAG, "Error loading avatar", e) }) } return view } @@ -70,7 +70,7 @@ class AccountSpinnerAdapter(context: Context, accounts: List<Account>) : val holder: ViewHolder var rowView = convertView if (rowView == null) { - holder = ViewHolder(ItemToolbarSpinnerBinding.inflate(mInflater, parent, false)) + holder = ViewHolder(ItemToolbarSpinnerBinding.inflate(mInflater, parent, false), disposable) rowView = holder.binding.root rowView.setTag(holder) } else { @@ -82,33 +82,29 @@ class AccountSpinnerAdapter(context: Context, accounts: List<Account>) : if (type == TYPE_ACCOUNT) { val account = getItem(position)!! val ip2ipString = rowView.context.getString(R.string.account_type_ip2ip) - holder.loader.add(account.accountAlias - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { alias -> - val subtitle = getUri(account, ip2ipString) - holder.binding.title.text = alias.ifEmpty { context.getString(R.string.ring_account) } - if (alias == subtitle) { - holder.binding.subtitle.visibility = View.GONE - } else { - holder.binding.subtitle.visibility = View.VISIBLE - holder.binding.subtitle.text = subtitle - } - }) val params = holder.binding.title.layoutParams as RelativeLayout.LayoutParams params.removeRule(RelativeLayout.CENTER_VERTICAL) holder.binding.title.layoutParams = params logoParam.width = logoSize logoParam.height = logoSize holder.binding.logo.layoutParams = logoParam - holder.loader.add(AvatarDrawable.load(context, account) - .subscribeOn(Schedulers.io()) + holder.loader.add(VCardServiceImpl.loadProfile(context, account) + .map { profile -> Profile(profile.displayName ?: account.alias, AvatarDrawable.build(context, account, profile)) } .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ avatar -> holder.binding.logo.setImageDrawable(avatar) }) - { e -> Log.e(TAG, "Error loading avatar", e) }) + .subscribe({ profile -> + val subtitle = getUri(account, ip2ipString) + holder.binding.logo.setImageDrawable(profile.avatar as AvatarDrawable) + holder.binding.title.text = profile.displayName?.ifEmpty { context.getString(R.string.ring_account) } + if (profile.displayName == subtitle) { + holder.binding.subtitle.visibility = View.GONE + } else { + holder.binding.subtitle.visibility = View.VISIBLE + holder.binding.subtitle.text = subtitle + } + }){ e: Throwable -> Log.e(TAG, "Error loading avatar", e) }) } else { holder.binding.title.setText( if (type == TYPE_CREATE_JAMI) R.string.add_ring_account_title else R.string.add_sip_account_title) - holder.binding.subtitle.visibility = View.GONE holder.binding.logo.setImageResource(R.drawable.baseline_add_24) logoParam.width = ViewGroup.LayoutParams.WRAP_CONTENT @@ -134,12 +130,12 @@ class AccountSpinnerAdapter(context: Context, accounts: List<Account>) : return super.getCount() + 2 } - private class ViewHolder(val binding: ItemToolbarSpinnerBinding) { - val loader = CompositeDisposable() + private class ViewHolder(val binding: ItemToolbarSpinnerBinding, parentDisposable: CompositeDisposable) { + val loader = CompositeDisposable().apply { parentDisposable.add(this) } } - private class ViewHolderHeader(val binding: ItemToolbarSelectedBinding) { - val loader = CompositeDisposable() + private class ViewHolderHeader(val binding: ItemToolbarSelectedBinding, parentDisposable: CompositeDisposable) { + val loader = CompositeDisposable().apply { parentDisposable.add(this) } } private fun getUri(account: Account, defaultNameSip: CharSequence): String { diff --git a/ring-android/app/src/main/java/cx/ring/client/ContactDetailsActivity.kt b/ring-android/app/src/main/java/cx/ring/client/ContactDetailsActivity.kt index 681b9b4f553eab5c5ff697bdae6b5eb09df4779a..3b79991a9579011f8af0ddc961df1a274f8fcaae 100644 --- a/ring-android/app/src/main/java/cx/ring/client/ContactDetailsActivity.kt +++ b/ring-android/app/src/main/java/cx/ring/client/ContactDetailsActivity.kt @@ -38,6 +38,7 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar import cx.ring.R +import cx.ring.application.JamiApplication import cx.ring.client.ColorChooserBottomSheet.IColorSelected import cx.ring.client.EmojiChooserBottomSheet.IEmojiSelected import cx.ring.databinding.ActivityContactDetailsBinding @@ -116,9 +117,7 @@ class ContactDetailsActivity : AppCompatActivity() { internal class ContactActionView( val binding: ItemContactActionBinding, parentDisposable: CompositeDisposable - ) : RecyclerView.ViewHolder( - binding.root - ) { + ) : RecyclerView.ViewHolder(binding.root) { var callback: (() -> Unit)? = null val disposable = CompositeDisposable() @@ -240,6 +239,7 @@ class ContactDetailsActivity : AppCompatActivity() { finish() return } + JamiApplication.instance?.startDaemon() binding = ActivityContactDetailsBinding.inflate(layoutInflater) setContentView(binding!!.root) //JamiApplication.getInstance().getInjectionComponent().inject(this); @@ -285,11 +285,7 @@ class ContactDetailsActivity : AppCompatActivity() { //adapter.actions.add(new ContactAction(R.drawable.baseline_info_24, getText(infoString), () -> {})); binding!!.conversationType.setText(infoString) //binding.conversationType.setCompoundDrawables(getDrawable(infoIcon), null, null, null); - colorAction = ContactAction( - R.drawable.item_color_background, - 0, - getText(R.string.conversation_preference_color) - ) { + colorAction = ContactAction(R.drawable.item_color_background, 0, getText(R.string.conversation_preference_color)) { val frag = ColorChooserBottomSheet() frag.setCallback(object : IColorSelected { override fun onColorSelected(color: Int) { diff --git a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.kt b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.kt index c0e072437c41a55c29dacfe48c41f2bd56f13811..6427e7b2f4c65f8987bfbcac4fa065ff30be7813 100644 --- a/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.kt +++ b/ring-android/app/src/main/java/cx/ring/client/ConversationActivity.kt @@ -58,7 +58,7 @@ class ConversationActivity : AppCompatActivity(), Colorable { } conversationPath = path; val isBubble = getIntent().getBooleanExtra(NotificationServiceImpl.EXTRA_BUBBLE, false) - JamiApplication.instance!!.startDaemon() + JamiApplication.instance?.startDaemon() val binding = ActivityConversationBinding.inflate(layoutInflater) setContentView(binding.root) setSupportActionBar(binding.mainToolbar) diff --git a/ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.kt b/ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.kt index 801864f84d8a048b1d55031f408933c29c4e5d1c..9176a1231357a0924a7b9cfee13187cbc278a884 100644 --- a/ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.kt +++ b/ring-android/app/src/main/java/cx/ring/client/ConversationSelectionActivity.kt @@ -20,12 +20,12 @@ package cx.ring.client import android.content.Intent import android.os.Bundle -import android.text.TextUtils import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import cx.ring.R import cx.ring.adapters.SmartListAdapter +import cx.ring.application.JamiApplication import cx.ring.fragments.CallFragment import cx.ring.utils.ConversationPath import cx.ring.viewholders.SmartListViewHolder.SmartListListeners @@ -53,14 +53,14 @@ class ConversationSelectionActivity : AppCompatActivity() { var mCallService: CallService private val adapter: SmartListAdapter = SmartListAdapter(null, object : SmartListListeners { - override fun onItemClick(smartListViewModel: SmartListViewModel) { + override fun onItemClick(item: SmartListViewModel) { val intent = Intent() - intent.data = ConversationPath.toUri(smartListViewModel.accountId, smartListViewModel.uri) + intent.data = ConversationPath.toUri(item.accountId, item.uri) setResult(RESULT_OK, intent) finish() } - override fun onItemLongClick(smartListViewModel: SmartListViewModel) {} + override fun onItemLongClick(item: SmartListViewModel) {} }, mDisposable) override fun onCreate(savedInstanceState: Bundle?) { @@ -69,6 +69,7 @@ class ConversationSelectionActivity : AppCompatActivity() { val list = findViewById<RecyclerView>(R.id.conversationList) list.layoutManager = LinearLayoutManager(this) list.adapter = adapter + JamiApplication.instance?.startDaemon() } public override fun onStart() { diff --git a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.kt b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.kt index 40643d7b67822c38d33ad102f41446a54dba833b..dafd6843d8f3104b9325be2b7328609a12fcbc7c 100644 --- a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.kt +++ b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.kt @@ -169,11 +169,8 @@ class HomeActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen val extra = intent.extras val action = intent.action if (ACTION_PRESENT_TRUST_REQUEST_FRAGMENT == action) { - if (extra?.getString(ContactRequestsFragment.ACCOUNT_ID) == null) { - return - } //mAccountWithPendingrequests = extra.getString(ContactRequestsFragment.ACCOUNT_ID); - presentTrustRequestFragment(extra.getString(ContactRequestsFragment.ACCOUNT_ID)) + presentTrustRequestFragment(extra?.getString(ContactRequestsFragment.ACCOUNT_ID) ?: return) } else if (Intent.ACTION_SEND == action || Intent.ACTION_SEND_MULTIPLE == action) { val path = ConversationPath.fromBundle(extra) if (path != null) { @@ -261,7 +258,7 @@ class HomeActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen .observeOn(AndroidSchedulers.mainThread()) .subscribe({ accounts -> if (mAccountAdapter == null) { - mAccountAdapter = AccountSpinnerAdapter(this@HomeActivity, ArrayList(accounts)).apply { + mAccountAdapter = AccountSpinnerAdapter(this@HomeActivity, ArrayList(accounts), mDisposable).apply { setNotifyOnChange(false) mBinding?.spinnerToolbar?.adapter = this } @@ -375,7 +372,7 @@ class HomeActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen .commit() } - private fun presentTrustRequestFragment(accountID: String?) { + private fun presentTrustRequestFragment(accountID: String) { mNotificationService.cancelTrustRequestNotification(accountID) if (fContent is ContactRequestsFragment) { (fContent as ContactRequestsFragment).presentForAccount(accountID) @@ -635,7 +632,7 @@ class HomeActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen * Changes the current main fragment to a plugin settings fragment * @param pluginDetails */ - fun gotToPluginSettings(pluginDetails: PluginDetails?) { + fun gotToPluginSettings(pluginDetails: PluginDetails) { if (fContent is PluginSettingsFragment) { return } @@ -651,7 +648,7 @@ class HomeActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen /** * Changes the current main fragment to a plugin PATH preference fragment */ - fun gotToPluginPathPreference(pluginDetails: PluginDetails?, preferenceKey: String?) { + fun gotToPluginPathPreference(pluginDetails: PluginDetails, preferenceKey: String) { if (fContent is PluginPathPreferenceFragment) { return } diff --git a/ring-android/app/src/main/java/cx/ring/client/MediaViewerFragment.kt b/ring-android/app/src/main/java/cx/ring/client/MediaViewerFragment.kt index 77c2d82717c71addfc3ec5c25c7708faf82065e2..da2ef8bc1a0ac84269126eb8af500f9c29b525c0 100644 --- a/ring-android/app/src/main/java/cx/ring/client/MediaViewerFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/client/MediaViewerFragment.kt @@ -38,11 +38,7 @@ class MediaViewerFragment : Fragment() { private var mUri: Uri? = null private var mImage: ImageView? = null - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val view = inflater.inflate(R.layout.fragment_media_viewer, container, false) as ViewGroup mImage = view.findViewById(R.id.image) showImage() diff --git a/ring-android/app/src/main/java/cx/ring/client/RingtoneActivity.kt b/ring-android/app/src/main/java/cx/ring/client/RingtoneActivity.kt index e604f5a7c098525d1e915da12ef46ff86419125c..fb12c01aea1100c0d435e1b5cb0effa7e51737d5 100644 --- a/ring-android/app/src/main/java/cx/ring/client/RingtoneActivity.kt +++ b/ring-android/app/src/main/java/cx/ring/client/RingtoneActivity.kt @@ -24,6 +24,7 @@ import android.content.DialogInterface import android.content.Intent import android.media.MediaPlayer import android.os.Bundle +import android.util.Log import android.view.View import android.widget.ImageView import android.widget.TextView @@ -48,7 +49,6 @@ import net.jami.model.Account import net.jami.model.ConfigKey import net.jami.model.Ringtone import net.jami.services.AccountService -import net.jami.utils.Log import java.io.File import java.io.IOException import java.util.* @@ -57,11 +57,11 @@ import javax.inject.Singleton @AndroidEntryPoint class RingtoneActivity : AppCompatActivity() { - private var adapter: RingtoneAdapter? = null + private lateinit var adapter: RingtoneAdapter private lateinit var mAccount: Account - private var customRingtone: TextView? = null - private var customPlaying: ImageView? = null - private var customSelected: ImageView? = null + private lateinit var customRingtone: TextView + private lateinit var customPlaying: ImageView + private lateinit var customSelected: ImageView private val mediaPlayer: MediaPlayer = MediaPlayer() private var disposable: Disposable? = null @@ -78,28 +78,27 @@ class RingtoneActivity : AppCompatActivity() { return } mAccount = account - - /*Toolbar toolbar = findViewById(R.id.ringtoneToolbar); - toolbar.setNavigationOnClickListener(view -> finish());*/ - val recycler = findViewById<RecyclerView>(R.id.ringToneRecycler) - val customRingtoneLayout = findViewById<ConstraintLayout>(R.id.customRingtoneLayout) customRingtone = findViewById(R.id.customRingtoneName) customPlaying = findViewById(R.id.custom_ringtone_playing) customSelected = findViewById(R.id.custom_ringtone_selected) adapter = RingtoneAdapter(prepareRingtones()) val upcomingLayoutManager: LayoutManager = LinearLayoutManager(this) + + val recycler = findViewById<RecyclerView>(R.id.ringToneRecycler) recycler.layoutManager = upcomingLayoutManager recycler.itemAnimator = DefaultItemAnimator() recycler.adapter = adapter // loads the user's settings setPreference() + + val customRingtoneLayout = findViewById<ConstraintLayout>(R.id.customRingtoneLayout) customRingtoneLayout.setOnClickListener { displayFileSearchDialog() } customRingtoneLayout.setOnLongClickListener { displayRemoveDialog() true } - disposable = adapter!!.ringtoneSubject.subscribe({ ringtone: Ringtone -> + disposable = adapter.getRingtone().subscribe({ ringtone: Ringtone -> setJamiRingtone(ringtone) removeCustomRingtone() }) { Log.e(TAG, "Error updating ringtone status") } @@ -117,18 +116,17 @@ class RingtoneActivity : AppCompatActivity() { override fun finish() { super.finish() - adapter!!.releaseMediaPlayer() + adapter.releaseMediaPlayer() mediaPlayer.release() } private fun prepareRingtones(): List<Ringtone> { val ringtoneList: MutableList<Ringtone> = ArrayList() val ringtoneFolder = File(filesDir, "ringtones") - val ringtones = ringtoneFolder.listFiles() - val ringtoneIcon = getDrawable(R.drawable.baseline_notifications_active_24) - if (ringtones == null) return ringtoneList + val ringtones = ringtoneFolder.listFiles() ?: return emptyList() + val ringtoneIcon = getDrawable(R.drawable.baseline_notifications_active_24)!! Arrays.sort(ringtones) { a: File, b: File -> a.name.compareTo(b.name) } - ringtoneList.add(Ringtone("Silent", null, getDrawable(R.drawable.baseline_notifications_off_24))) + ringtoneList.add(Ringtone("Silent", null, getDrawable(R.drawable.baseline_notifications_off_24)!!)) for (file in ringtones) { val name = stripFileNameExtension(file.name) ringtoneList.add(Ringtone(name, file.absolutePath, ringtoneIcon)) @@ -143,10 +141,10 @@ class RingtoneActivity : AppCompatActivity() { val path = File(mAccount.config[ConfigKey.RINGTONE_PATH]) val customEnabled = mAccount.config.getBool(ConfigKey.RINGTONE_CUSTOM) if (customEnabled && path.exists()) { - customRingtone!!.text = path.name - customSelected!!.visibility = View.VISIBLE + customRingtone.text = path.name + customSelected.visibility = View.VISIBLE } else if (path.exists()) { - adapter!!.selectDefaultItem(path.absolutePath, mAccount.config.getBool(ConfigKey.RINGTONE_ENABLED)) + adapter.selectDefaultItem(path.absolutePath, mAccount.config.getBool(ConfigKey.RINGTONE_ENABLED)) } else { setDefaultRingtone() } @@ -155,7 +153,7 @@ class RingtoneActivity : AppCompatActivity() { private fun setDefaultRingtone() { val ringtonesDir = File(filesDir, "ringtones") val ringtonePath = File(ringtonesDir, getString(R.string.ringtone_default_name)).absolutePath - adapter!!.selectDefaultItem(ringtonePath, mAccount.config.getBool(ConfigKey.RINGTONE_ENABLED)) + adapter.selectDefaultItem(ringtonePath, mAccount.config.getBool(ConfigKey.RINGTONE_ENABLED)) mAccount.setDetail(ConfigKey.RINGTONE_PATH, ringtonePath) mAccount.setDetail(ConfigKey.RINGTONE_CUSTOM, false) updateAccount() @@ -225,9 +223,9 @@ class RingtoneActivity : AppCompatActivity() { * Removes a custom ringtone and updates the view */ private fun removeCustomRingtone() { - customSelected!!.visibility = View.INVISIBLE - customPlaying!!.visibility = View.INVISIBLE - customRingtone!!.setText(R.string.ringtone_custom_prompt) + customSelected.visibility = View.INVISIBLE + customPlaying.visibility = View.INVISIBLE + customRingtone.setText(R.string.ringtone_custom_prompt) stopCustomPreview() } @@ -252,10 +250,10 @@ class RingtoneActivity : AppCompatActivity() { displayFileTooBigDialog() } else { // resetState will stop the preview - adapter!!.resetState() - customRingtone!!.text = ringtone.name - customSelected!!.visibility = View.VISIBLE - customPlaying!!.visibility = View.VISIBLE + adapter.resetState() + customRingtone.text = ringtone.name + customSelected.visibility = View.VISIBLE + customPlaying.visibility = View.VISIBLE Glide.with(this) .load(R.raw.baseline_graphic_eq_black_24dp) .placeholder(R.drawable.baseline_graphic_eq_24) @@ -317,14 +315,8 @@ class RingtoneActivity : AppCompatActivity() { cr.takePersistableUriPermission(uri, takeFlags) AndroidFileUtils.getCacheFile(this, uri) .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { ringtone: File -> onFileFound(ringtone) } - ) { - Toast.makeText( - this, - "Can't load ringtone !", - Toast.LENGTH_SHORT - ).show() + .subscribe({ ringtone: File -> onFileFound(ringtone) }) { + Toast.makeText(this, "Can't load ringtone !", Toast.LENGTH_SHORT).show() } } } @@ -342,13 +334,12 @@ class RingtoneActivity : AppCompatActivity() { */ fun stripFileNameExtension(fileName: String): String { val index = fileName.lastIndexOf('.') - return if (index == -1) { + return if (index == -1) fileName - } else { + else fileName.substring(0, index) - } } - private val TAG = RingtoneActivity::class.java.simpleName + private val TAG = RingtoneActivity::class.simpleName!! } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/client/ShareActivity.kt b/ring-android/app/src/main/java/cx/ring/client/ShareActivity.kt index f996fbf030036a8d191b07ecd9a26dd3bf33cc57..7e5649a97f35d50c09befb5ae97674982e97bfb8 100644 --- a/ring-android/app/src/main/java/cx/ring/client/ShareActivity.kt +++ b/ring-android/app/src/main/java/cx/ring/client/ShareActivity.kt @@ -22,6 +22,7 @@ import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import cx.ring.utils.ConversationPath import cx.ring.R +import cx.ring.application.JamiApplication import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint @@ -36,6 +37,7 @@ class ShareActivity : AppCompatActivity() { finish() return } + JamiApplication.instance?.startDaemon() setContentView(R.layout.activity_share) } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/contactrequests/ContactRequestsFragment.kt b/ring-android/app/src/main/java/cx/ring/contactrequests/ContactRequestsFragment.kt index 0d53731031188b2299030b88116d1207663ec957..fe14ee55c5af3e2231c3921fab38f262a8b04c50 100644 --- a/ring-android/app/src/main/java/cx/ring/contactrequests/ContactRequestsFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/contactrequests/ContactRequestsFragment.kt @@ -106,11 +106,11 @@ class ContactRequestsFragment : (requireActivity() as HomeActivity).startConversation(accountId, contactId) } - override fun onItemClick(smartListViewModel: SmartListViewModel) { - presenter.contactRequestClicked(smartListViewModel.accountId, smartListViewModel.uri) + override fun onItemClick(item: SmartListViewModel) { + presenter.contactRequestClicked(item.accountId, item.uri) } - override fun onItemLongClick(smartListViewModel: SmartListViewModel) {} + override fun onItemLongClick(item: SmartListViewModel) {} companion object { private val TAG = ContactRequestsFragment::class.java.simpleName diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AccountMigrationFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/AccountMigrationFragment.java index b5c3691147be6768d00991912b167b74c2df484f..74e7a117ac797056d3c5efe75b56635b51316c25 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/AccountMigrationFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/AccountMigrationFragment.java @@ -43,6 +43,7 @@ import androidx.fragment.app.Fragment; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import cx.ring.R; +import cx.ring.account.AccountEditionFragment; import cx.ring.application.JamiApplication; import cx.ring.databinding.FragAccountMigrationBinding; import dagger.hilt.android.AndroidEntryPoint; @@ -56,7 +57,6 @@ import net.jami.services.AccountService; @AndroidEntryPoint public class AccountMigrationFragment extends Fragment { - public static final String ACCOUNT_ID = "ACCOUNT_ID"; static final String TAG = AccountMigrationFragment.class.getSimpleName(); @Inject AccountService mAccountService; @@ -99,7 +99,7 @@ public class AccountMigrationFragment extends Fragment { public void onResume() { super.onResume(); if (getArguments() != null) { - mAccountId = getArguments().getString(ACCOUNT_ID); + mAccountId = getArguments().getString(AccountEditionFragment.ACCOUNT_ID_KEY); } } diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountFragment.kt index 98c0cbab3ff75e35be52068b5d1c22cabd2a8eff..c3a5f0518914ba1eda87a4356addb21684781b5b 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountFragment.kt @@ -35,18 +35,20 @@ import cx.ring.views.PasswordPreference import dagger.hilt.android.AndroidEntryPoint import net.jami.model.AccountConfig import net.jami.model.ConfigKey +import net.jami.settings.AdvancedAccountPresenter +import net.jami.settings.AdvancedAccountView @AndroidEntryPoint class AdvancedAccountFragment : BasePreferenceFragment<AdvancedAccountPresenter>(), AdvancedAccountView, Preference.OnPreferenceChangeListener { - override fun onCreatePreferences(bundle: Bundle?, rootKey: String?) { - super.onCreatePreferences(bundle, rootKey) + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + super.onCreatePreferences(savedInstanceState, rootKey) // Load the preferences from an XML resource addPreferencesFromResource(R.xml.account_advanced_prefs) val args = arguments - presenter!!.init(args?.getString(AccountEditionFragment.ACCOUNT_ID_KEY)) + presenter.init(args?.getString(AccountEditionFragment.ACCOUNT_ID_KEY)) } override fun onDisplayPreferenceDialog(preference: Preference) { @@ -79,18 +81,18 @@ class AdvancedAccountFragment : BasePreferenceFragment<AdvancedAccountPresenter> if (pref != null) { pref.onPreferenceChangeListener = this if (confKey == ConfigKey.LOCAL_INTERFACE) { - val `val` = config[confKey] + val value = config[confKey] val display = networkInterfaces.toTypedArray() val listPref = pref as ListPreference listPref.entries = display listPref.entryValues = display - listPref.summary = `val` - listPref.value = `val` + listPref.summary = value + listPref.value = value } else if (!confKey.isTwoState) { - val `val` = config[confKey] - pref.summary = `val` + val value = config[confKey] + pref.summary = value if (pref is EditTextPreference) { - pref.text = `val` + pref.text = value } } else { (pref as TwoStatePreference).isChecked = config.getBool(confKey) @@ -117,18 +119,18 @@ class AdvancedAccountFragment : BasePreferenceFragment<AdvancedAccountPresenter> } override fun onPreferenceChange(preference: Preference, newValue: Any): Boolean { - val key = ConfigKey.fromString(preference.key) - presenter!!.preferenceChanged(key, newValue) + val key = ConfigKey.fromString(preference.key)!! + presenter.preferenceChanged(key, newValue) when (preference) { is TwoStatePreference -> { - presenter!!.twoStatePreferenceChanged(key, newValue) + presenter.twoStatePreferenceChanged(key, newValue) } is PasswordPreference -> { - presenter!!.passwordPreferenceChanged(key, newValue) + presenter.passwordPreferenceChanged(key, newValue) preference.setSummary(if (TextUtils.isEmpty(newValue.toString())) "" else "******") } else -> { - presenter!!.preferenceChanged(key, newValue) + presenter.preferenceChanged(key, newValue) preference.summary = newValue.toString() } } @@ -136,8 +138,7 @@ class AdvancedAccountFragment : BasePreferenceFragment<AdvancedAccountPresenter> } companion object { - @JvmField - val TAG = AdvancedAccountFragment::class.java.simpleName + val TAG = AdvancedAccountFragment::class.simpleName!! private const val DIALOG_FRAGMENT_TAG = "androidx.preference.PreferenceFragment.DIALOG" } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountPresenter.java b/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountPresenter.java deleted file mode 100644 index 4d2569e4ae82bdda9ce5e54e6d95f3942a835a69..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountPresenter.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.fragments; - -import android.util.Log; - -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.ArrayList; -import java.util.Enumeration; - -import javax.inject.Inject; - -import net.jami.services.ConversationFacade; -import net.jami.model.Account; -import net.jami.model.ConfigKey; -import net.jami.mvp.RootPresenter; -import net.jami.services.AccountService; - -public class AdvancedAccountPresenter extends RootPresenter<AdvancedAccountView> { - - public static final String TAG = AdvancedAccountPresenter.class.getSimpleName(); - - protected ConversationFacade mConversationFacade; - protected AccountService mAccountService; - - private Account mAccount; - - @Inject - public AdvancedAccountPresenter(ConversationFacade conversationFacade, AccountService accountService) { - this.mConversationFacade = conversationFacade; - this.mAccountService = accountService; - } - - public void init(String accountId) { - mAccount = mAccountService.getAccount(accountId); - if (mAccount != null) { - getView().initView(mAccount.getConfig(), getNetworkInterfaces()); - } - } - - public void twoStatePreferenceChanged(ConfigKey configKey, Object newValue) { - mAccount.setDetail(configKey, newValue.toString()); - updateAccount(); - } - - public void passwordPreferenceChanged(ConfigKey configKey, Object newValue) { - mAccount.setDetail(configKey, newValue.toString()); - updateAccount(); - } - - public void preferenceChanged(ConfigKey configKey, Object newValue) { - if (configKey == ConfigKey.AUDIO_PORT_MAX || configKey == ConfigKey.AUDIO_PORT_MIN) { - newValue = adjustRtpRange(Integer.valueOf((String) newValue)); - } - mAccount.setDetail(configKey, newValue.toString()); - updateAccount(); - } - - private void updateAccount() { - mAccountService.setCredentials(mAccount.getAccountID(), mAccount.getCredentialsHashMapList()); - mAccountService.setAccountDetails(mAccount.getAccountID(), mAccount.getDetails()); - } - - private String adjustRtpRange(int newValue) { - if (newValue < 1024) { - return "1024"; - } - - if (newValue > 65534) { - return "65534"; - } - - return String.valueOf(newValue); - } - - private ArrayList<CharSequence> getNetworkInterfaces() { - ArrayList<CharSequence> result = new ArrayList<>(); - result.add("default"); - try { - - for (Enumeration<NetworkInterface> list = NetworkInterface.getNetworkInterfaces(); list.hasMoreElements(); ) { - NetworkInterface i = list.nextElement(); - if (i.isUp()) { - result.add(i.getDisplayName()); - } - } - } catch (SocketException e) { - Log.e(TAG, "Error enumerating interfaces: ", e); - } - return result; - } -} diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.kt index 0ffe99af78d34c1d5ea675bc30faf68723bc769d..9b3b0587eaf4bff0eb324263edacee241062bb80 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.kt @@ -770,17 +770,16 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView, var display = display Log.w(TAG, "displayHangupButton $display") display = display and !isChoosePluginMode - binding!!.callControlGroup.visibility = if (display) View.VISIBLE else View.GONE - binding!!.callHangupBtn.visibility = - if (display) View.VISIBLE else View.GONE - binding!!.confControlGroup.visibility = - if (mConferenceMode && display) View.VISIBLE else View.GONE + binding?.apply { + callControlGroup.visibility = if (display) View.VISIBLE else View.GONE + callHangupBtn.visibility = if (display) View.VISIBLE else View.GONE + confControlGroup.visibility = if (mConferenceMode && display) View.VISIBLE else View.GONE + } } override fun displayDialPadKeyboard() { binding!!.dialpadEditText.requestFocus() - val imm = - binding!!.dialpadEditText.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + val imm = binding!!.dialpadEditText.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY) } @@ -789,8 +788,7 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView, } override fun updateAudioState(state: AudioState) { - binding!!.callSpeakerBtn.isChecked = - state.outputType == HardwareService.AudioOutput.SPEAKERS + binding!!.callSpeakerBtn.isChecked = state.outputType == HardwareService.AudioOutput.SPEAKERS } override fun updateMenu() { @@ -799,8 +797,7 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView, override fun updateTime(duration: Long) { binding?.let { binding -> - if (duration <= 0) binding.callStatusTxt.text = - null else binding.callStatusTxt.text = String.format( + binding.callStatusTxt.text = if (duration <= 0) null else String.format( Locale.getDefault(), "%d:%02d:%02d", duration / 3600, @@ -1189,7 +1186,7 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView, presenter.speakerClick(binding!!.callSpeakerBtn.isChecked) } - private fun startScreenShare(mediaProjection: MediaProjection) { + private fun startScreenShare(mediaProjection: MediaProjection?) { if (presenter.startScreenShare(mediaProjection)) { if (isChoosePluginMode) { binding!!.pluginPreviewSurface.visibility = View.GONE @@ -1324,8 +1321,8 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView, if (pluginsModeFirst) { // Init val callMediaHandlers = JamiService.getCallMediaHandlers() - val videoPluginsItems: MutableList<Drawable?> = ArrayList(callMediaHandlers.size + 1) - videoPluginsItems.add(context.getDrawable(R.drawable.baseline_cancel_24)) + val videoPluginsItems: MutableList<Drawable> = ArrayList(callMediaHandlers.size + 1) + videoPluginsItems.add(context.getDrawable(R.drawable.baseline_cancel_24)!!) // Search for plugin call media handlers icons // If a call media handler doesn't have an icon use a standard android icon for (callMediaHandler in callMediaHandlers) { @@ -1333,10 +1330,7 @@ class CallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView, var drawablePath = details["iconPath"] if (drawablePath != null && drawablePath.endsWith("svg")) drawablePath = drawablePath.replace(".svg", ".png") - var handlerIcon = Drawable.createFromPath(drawablePath) - if (handlerIcon == null) { - handlerIcon = context.getDrawable(R.drawable.ic_jami) - } + val handlerIcon = Drawable.createFromPath(drawablePath) ?: context.getDrawable(R.drawable.ic_jami)!! videoPluginsItems.add(handlerIcon) } rp!!.updateData(videoPluginsItems) diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CodecPreference.java b/ring-android/app/src/main/java/cx/ring/fragments/CodecPreference.java deleted file mode 100644 index 9a87bf776d71ceb05bdfc5eefd883ef5ca8024b2..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/fragments/CodecPreference.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.fragments; - -import android.content.Context; -import androidx.preference.Preference; -import androidx.preference.PreferenceViewHolder; -import androidx.recyclerview.widget.RecyclerView; -import android.util.AttributeSet; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.CheckBox; -import android.widget.LinearLayout; -import android.widget.ListAdapter; -import android.widget.ListView; -import android.widget.TextView; - -import java.util.ArrayList; - -import cx.ring.R; -import net.jami.model.Codec; - -class CodecPreference extends Preference { - private static final String TAG = CodecPreference.class.getSimpleName(); - - private final CodecAdapter listAdapter; - - public CodecPreference(Context context, AttributeSet attrs) { - super(context, attrs); - setWidgetLayoutResource(R.layout.frag_audio_mgmt); - listAdapter = new CodecAdapter(getContext()); - - } - - public CodecPreference(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - setWidgetLayoutResource(R.layout.frag_audio_mgmt); - listAdapter = new CodecAdapter(getContext()); - } - - private void setListViewHeight(ListView listView, LinearLayout llMain) { - ListAdapter listAdapter = listView.getAdapter(); - if (listAdapter == null) { - return; - } - - int totalHeight = 0; - int firstHeight; - int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.AT_MOST); - - for (int i = 0; i < listAdapter.getCount(); i++) { - View listItem = listAdapter.getView(i, null, listView); - listItem.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED); - firstHeight = listItem.getMeasuredHeight(); - totalHeight += firstHeight; - } - - RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) llMain.getLayoutParams(); - params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount())); - llMain.setLayoutParams(params); - } - - @Override - public void onBindViewHolder(PreferenceViewHolder holder) { - super.onBindViewHolder(holder); - - ListView mCodecList = (ListView) holder.findViewById(R.id.dndlistview); - mCodecList.setFocusable(false); - if (mCodecList.getAdapter() != listAdapter) - mCodecList.setAdapter(listAdapter); - mCodecList.setOnItemClickListener((arg0, arg1, pos, arg3) -> { - listAdapter.getItem(pos).toggleState(); - listAdapter.notifyDataSetChanged(); - callChangeListener(getActiveCodecList()); - }); - - setListViewHeight(mCodecList, (LinearLayout) mCodecList.getParent()); - } - - ArrayList<Long> getActiveCodecList() { - ArrayList<Long> results = new ArrayList<>(); - for (int i = 0; i < listAdapter.getCount(); ++i) { - if (listAdapter.getItem(i).isEnabled()) { - results.add(listAdapter.getItem(i).getPayload()); - } - } - return results; - } - - void setCodecs(ArrayList<Codec> codecs) { - listAdapter.setDataset(codecs); - notifyChanged(); - notifyHierarchyChanged(); - } - - void refresh() { - if (null != this.listAdapter) { - this.listAdapter.notifyDataSetChanged(); - } - } - - private static class CodecAdapter extends BaseAdapter { - private final ArrayList<Codec> items = new ArrayList<>(); - private Context mContext; - - CodecAdapter(Context context) { - mContext = context; - } - - @Override - public int getCount() { - return items.size(); - } - - @Override - public Codec getItem(int position) { - return items.get(position); - } - - @Override - public long getItemId(int position) { - return 0; - } - - @Override - public View getView(int pos, View convertView, ViewGroup parent) { - View rowView = convertView; - CodecView entryView; - - if (rowView == null) { - LayoutInflater inflater = LayoutInflater.from(mContext); - rowView = inflater.inflate(R.layout.item_codec, parent, false); - - entryView = new CodecView(); - entryView.name = rowView.findViewById(R.id.codec_name); - entryView.samplerate = rowView.findViewById(R.id.codec_samplerate); - entryView.enabled = rowView.findViewById(R.id.codec_checked); - rowView.setTag(entryView); - } else { - entryView = (CodecView) rowView.getTag(); - } - - Codec codec = items.get(pos); - - if (codec.isSpeex()) - entryView.samplerate.setVisibility(View.VISIBLE); - else - entryView.samplerate.setVisibility(View.GONE); - - entryView.name.setText(codec.getName()); - entryView.samplerate.setText(codec.getSampleRate()); - entryView.enabled.setChecked(codec.isEnabled()); - - return rowView; - } - - void setDataset(ArrayList<Codec> codecs) { - items.clear(); - items.addAll(codecs); - notifyDataSetChanged(); - } - - class CodecView { - public TextView name; - public TextView samplerate; - public CheckBox enabled; - } - } -} diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CodecPreference.kt b/ring-android/app/src/main/java/cx/ring/fragments/CodecPreference.kt new file mode 100644 index 0000000000000000000000000000000000000000..229c9838374b4049329255a171039411adb0b90e --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/fragments/CodecPreference.kt @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.fragments + +import android.content.Context +import android.util.AttributeSet +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.* +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import androidx.recyclerview.widget.RecyclerView +import cx.ring.R +import net.jami.model.Codec + +internal class CodecPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int = 0) : + Preference(context, attrs, defStyleAttr) { + private val listAdapter: CodecAdapter = CodecAdapter(context) + + private fun setListViewHeight(listView: ListView, llMain: LinearLayout) { + val listAdapter = listView.adapter ?: return + var totalHeight = 0 + var firstHeight: Int + val desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.width, View.MeasureSpec.AT_MOST) + for (i in 0 until listAdapter.count) { + val listItem = listAdapter.getView(i, null, listView) + listItem.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED) + firstHeight = listItem.measuredHeight + totalHeight += firstHeight + } + val params = llMain.layoutParams as RecyclerView.LayoutParams + params.height = totalHeight + listView.dividerHeight * listAdapter.count + llMain.layoutParams = params + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + val mCodecList = holder.findViewById(R.id.dndlistview) as ListView + mCodecList.isFocusable = false + if (mCodecList.adapter !== listAdapter) + mCodecList.adapter = listAdapter + mCodecList.onItemClickListener = AdapterView.OnItemClickListener { _, _, pos: Int, _ -> + listAdapter.getItem(pos).toggleState() + listAdapter.notifyDataSetChanged() + callChangeListener(activeCodecList) + } + setListViewHeight(mCodecList, mCodecList.parent as LinearLayout) + } + + val activeCodecList: ArrayList<Long> + get() { + val results = ArrayList<Long>() + for (i in 0 until listAdapter.count) { + if (listAdapter.getItem(i).isEnabled) { + results.add(listAdapter.getItem(i).payload) + } + } + return results + } + + fun setCodecs(codecs: ArrayList<Codec>) { + listAdapter.setDataset(codecs) + notifyChanged() + notifyHierarchyChanged() + } + + fun refresh() { + listAdapter.notifyDataSetChanged() + } + + private class CodecAdapter constructor(private val mContext: Context) : BaseAdapter() { + private val items = ArrayList<Codec>() + override fun getCount(): Int { + return items.size + } + + override fun getItem(position: Int): Codec { + return items[position] + } + + override fun getItemId(position: Int): Long { + return 0 + } + + override fun getView(pos: Int, convertView: View?, parent: ViewGroup): View { + val entryView: CodecView + var rowView = convertView + if (rowView == null) { + rowView = LayoutInflater.from(mContext).inflate(R.layout.item_codec, parent, false) + entryView = CodecView( + rowView.findViewById(R.id.codec_name), + rowView.findViewById(R.id.codec_samplerate), + rowView.findViewById(R.id.codec_checked) + ) + rowView.tag = entryView + } else { + entryView = rowView.tag as CodecView + } + val codec = items[pos] + entryView.name.text = codec.name + entryView.samplerate.visibility = if (codec.isSpeex) View.VISIBLE else View.GONE + entryView.samplerate.text = codec.sampleRate + entryView.enabled.isChecked = codec.isEnabled + return rowView!! + } + + fun setDataset(codecs: ArrayList<Codec>) { + items.clear() + items.addAll(codecs) + notifyDataSetChanged() + } + + class CodecView ( + val name: TextView, + val samplerate: TextView, + val enabled: CheckBox + ) + } + + init { + widgetLayoutResource = R.layout.frag_audio_mgmt + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ContactPickerFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/ContactPickerFragment.java deleted file mode 100644 index d60da326ea2a0f8a889dc2e51e7811c968f94d79..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/fragments/ContactPickerFragment.java +++ /dev/null @@ -1,170 +0,0 @@ -package cx.ring.fragments; - -import android.app.Dialog; -import android.os.Bundle; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.bottomsheet.BottomSheetDialog; -import com.google.android.material.bottomsheet.BottomSheetDialogFragment; -import com.google.android.material.chip.Chip; - -import java.util.HashSet; -import java.util.Set; - -import javax.inject.Inject; - -import cx.ring.R; -import cx.ring.adapters.SmartListAdapter; -import cx.ring.client.HomeActivity; -import cx.ring.databinding.FragContactPickerBinding; -import net.jami.services.ConversationFacade; -import net.jami.model.Contact; -import net.jami.smartlist.SmartListViewModel; -import cx.ring.viewholders.SmartListViewHolder; -import cx.ring.views.AvatarDrawable; -import dagger.hilt.android.AndroidEntryPoint; -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.disposables.CompositeDisposable; - -@AndroidEntryPoint -public class ContactPickerFragment extends BottomSheetDialogFragment { - - public static final String TAG = ContactPickerFragment.class.getSimpleName(); - - private FragContactPickerBinding binding = null; - private SmartListAdapter adapter; - private final CompositeDisposable mDisposableBag = new CompositeDisposable(); - - private String mAccountId = null; - private final Set<Contact> mCurrentSelection = new HashSet<>(); - - @Inject - ConversationFacade mConversationFacade; - - public ContactPickerFragment() { - // Required empty public constructor - } - - public static ContactPickerFragment newInstance() { - return new ContactPickerFragment(); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - //setRetainInstance(true); - //((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this); - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - - } - - @NonNull - @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - Dialog bdialog = super.onCreateDialog(savedInstanceState); - if (bdialog instanceof BottomSheetDialog) { - BottomSheetDialog dialog = (BottomSheetDialog) bdialog; - BottomSheetBehavior<FrameLayout> behavior = dialog.getBehavior(); - behavior.setFitToContents(false); - behavior.setSkipCollapsed(true); - behavior.setState(BottomSheetBehavior.STATE_HALF_EXPANDED); - } - return bdialog; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - mDisposableBag.add(mConversationFacade.getContactList() - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(conversations -> { - if (binding == null) - return; - adapter.update(conversations); - })); - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = FragContactPickerBinding.inflate(getLayoutInflater(), container, false); - adapter = new SmartListAdapter(null, new SmartListViewHolder.SmartListListeners() { - @Override - public void onItemClick(SmartListViewModel item) { - mAccountId = item.getAccountId(); - - boolean checked = !item.isChecked(); - item.setChecked(checked); - adapter.update(item); - - Runnable remover = () -> { - mCurrentSelection.remove(item.getContacts().get(0)); - if (mCurrentSelection.isEmpty()) - binding.createGroupBtn.setEnabled(false); - item.setChecked(false); - adapter.update(item); - View v = binding.selectedContacts.findViewWithTag(item); - if (v != null) - binding.selectedContacts.removeView(v); - }; - - if (checked) { - if (mCurrentSelection.add(item.getContacts().get(0))) { - Chip chip = new Chip(requireContext(), null, R.style.Widget_MaterialComponents_Chip_Entry); - chip.setText(item.getContactName()); - chip.setChipIcon(new AvatarDrawable.Builder() - .withViewModel(item) - .withCircleCrop(true) - .withCheck(false) - .build(binding.getRoot().getContext())); - chip.setCloseIconVisible(true); - chip.setTag(item); - chip.setOnCloseIconClickListener(v -> remover.run()); - binding.selectedContacts.addView(chip); - } - binding.createGroupBtn.setEnabled(true); - } else { - remover.run(); - } - } - - @Override - public void onItemLongClick(SmartListViewModel item) { - - } - }, mDisposableBag); - binding.createGroupBtn.setOnClickListener(v -> mDisposableBag.add(mConversationFacade.createConversation(mAccountId, mCurrentSelection) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(conversation -> { - ((HomeActivity) requireActivity()).startConversation(mAccountId, conversation.getUri()); - Dialog dialog = getDialog(); - if (dialog != null) - dialog.cancel(); - }))); - binding.contactList.setAdapter(adapter); - return binding.getRoot(); - } - - @Override - public void onDestroyView() { - mDisposableBag.clear(); - binding = null; - adapter = null; - super.onDestroyView(); - } - - @Override - public int getTheme() { - return R.style.BottomSheetDialogTheme; - } -} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ContactPickerFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/ContactPickerFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..d458b0de1afeb7dbb6afdb2121f4a07b0fd56998 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/fragments/ContactPickerFragment.kt @@ -0,0 +1,138 @@ +package cx.ring.fragments + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.google.android.material.chip.Chip +import cx.ring.R +import cx.ring.adapters.SmartListAdapter +import cx.ring.client.HomeActivity +import cx.ring.databinding.FragContactPickerBinding +import cx.ring.viewholders.SmartListViewHolder.SmartListListeners +import cx.ring.views.AvatarDrawable +import dagger.hilt.android.AndroidEntryPoint +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.disposables.CompositeDisposable +import net.jami.model.Contact +import net.jami.model.Conversation +import net.jami.services.ConversationFacade +import net.jami.smartlist.SmartListViewModel +import java.util.* +import javax.inject.Inject + +@AndroidEntryPoint +class ContactPickerFragment : BottomSheetDialogFragment() { + private var binding: FragContactPickerBinding? = null + private var adapter: SmartListAdapter? = null + private val mDisposableBag = CompositeDisposable() + private var mAccountId: String? = null + private val mCurrentSelection: MutableSet<Contact> = HashSet() + + @JvmField + @Inject + var mConversationFacade: ConversationFacade? = null + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + //setRetainInstance(true); + //((JamiApplication) getActivity().getApplication()).getInjectionComponent().inject(this); + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val bdialog = super.onCreateDialog(savedInstanceState) + if (bdialog is BottomSheetDialog) { + val behavior = bdialog.behavior + behavior.isFitToContents = false + behavior.skipCollapsed = true + behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED + } + return bdialog + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + mDisposableBag.add(mConversationFacade!!.contactList + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { conversations: MutableList<SmartListViewModel> -> + if (binding == null) return@subscribe + adapter?.update(conversations) + }) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + binding = FragContactPickerBinding.inflate(layoutInflater, container, false) + adapter = SmartListAdapter(null, object : SmartListListeners { + override fun onItemClick(item: SmartListViewModel) { + mAccountId = item.accountId + val checked = !item.isChecked + item.isChecked = checked + adapter!!.update(item) + val remover = Runnable { + mCurrentSelection.remove(item.contacts[0]) + if (mCurrentSelection.isEmpty()) binding!!.createGroupBtn.isEnabled = false + item.isChecked = false + adapter!!.update(item) + val v = binding!!.selectedContacts.findViewWithTag<View>(item) + if (v != null) binding!!.selectedContacts.removeView(v) + } + if (checked) { + if (mCurrentSelection.add(item.contacts[0])) { + val chip = Chip(requireContext(), null, R.style.Widget_MaterialComponents_Chip_Entry) + chip.text = item.contactName + chip.chipIcon = AvatarDrawable.Builder() + .withViewModel(item) + .withCircleCrop(true) + .withCheck(false) + .build(binding!!.root.context) + chip.isCloseIconVisible = true + chip.tag = item + chip.setOnCloseIconClickListener { v: View? -> remover.run() } + binding!!.selectedContacts.addView(chip) + } + binding!!.createGroupBtn.isEnabled = true + } else { + remover.run() + } + } + + override fun onItemLongClick(item: SmartListViewModel) {} + }, mDisposableBag) + binding!!.createGroupBtn.setOnClickListener { v: View? -> + mDisposableBag.add( + mConversationFacade!!.createConversation(mAccountId!!, mCurrentSelection) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { conversation: Conversation -> + (requireActivity() as HomeActivity).startConversation(conversation.accountId, conversation.uri) + val dialog = dialog + dialog?.cancel() + }) + } + binding!!.contactList.adapter = adapter + return binding!!.root + } + + override fun onDestroyView() { + mDisposableBag.clear() + binding = null + adapter = null + super.onDestroyView() + } + + override fun getTheme(): Int { + return R.style.BottomSheetDialogTheme + } + + companion object { + val TAG = ContactPickerFragment::class.java.simpleName + fun newInstance(): ContactPickerFragment { + return ContactPickerFragment() + } + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.kt index aa5f541734622893e977e4f29f1a1fced5e661c3..792b9e47d06e42f15ec68a0cadf4993c3f65150c 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.kt @@ -65,8 +65,8 @@ import cx.ring.interfaces.Colorable import cx.ring.mvp.BaseSupportFragment import cx.ring.service.DRingService import cx.ring.service.LocationSharingService -import cx.ring.service.LocationSharingService.LocalBinder import cx.ring.services.NotificationServiceImpl +import cx.ring.services.SharedPreferencesServiceImpl import cx.ring.services.SharedPreferencesServiceImpl.Companion.getConversationPreferences import cx.ring.utils.ActionHelper import cx.ring.utils.AndroidFileUtils.copyFileToUri @@ -77,7 +77,6 @@ import cx.ring.utils.AndroidFileUtils.getCacheFile import cx.ring.utils.AndroidFileUtils.getMimeTypeFromExtension import cx.ring.utils.AndroidFileUtils.getSpaceLeft import cx.ring.utils.ContentUriHandler -import cx.ring.utils.ContentUriHandler.getUriForFile import cx.ring.utils.ConversationPath import cx.ring.utils.DeviceUtils.isTablet import cx.ring.utils.MediaButtonsHelper.MediaButtonsHelperCallback @@ -156,6 +155,17 @@ class ConversationFragment : BaseSupportFragment<ConversationPresenter, Conversa } } + override fun displayErrorToast(error: Error) { + val errorString: String = when (error) { + Error.NO_INPUT -> getString(R.string.call_error_no_camera_no_microphone) + Error.INVALID_FILE -> getString(R.string.invalid_file) + Error.NOT_ABLE_TO_WRITE_FILE -> getString(R.string.not_able_to_write_file) + Error.NO_SPACE_LEFT -> getString(R.string.no_space_left_on_device) + else -> getString(R.string.generic_error) + } + Toast.makeText(requireContext(), errorString, Toast.LENGTH_LONG).show() + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { val res = resources marginPx = res.getDimensionPixelSize(R.dimen.conversation_message_input_margin) @@ -188,8 +198,7 @@ class ConversationFragment : BaseSupportFragment<ConversationPresenter, Conversa } binding.ongoingcallPane.visibility = View.GONE binding.msgInputTxt.setMediaListener { contentInfo: InputContentInfoCompat -> - startFileSend( - getCacheFile(requireContext(), contentInfo.contentUri) + startFileSend(getCacheFile(requireContext(), contentInfo.contentUri) .flatMapCompletable { file: File -> sendFile(file) } .doFinally { contentInfo.releasePermission() }) } @@ -469,7 +478,7 @@ class ConversationFragment : BaseSupportFragment<ConversationPresenter, Conversa putExtra("android.intent.extras.CAMERA_FACING", Camera.CameraInfo.CAMERA_FACING_FRONT) putExtra("android.intent.extras.LENS_FACING_FRONT", 1) putExtra("android.intent.extra.USE_FRONT_CAMERA", true) - putExtra(MediaStore.EXTRA_OUTPUT, getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, createVideoFile(context).apply { + putExtra(MediaStore.EXTRA_OUTPUT, ContentUriHandler.getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, createVideoFile(context).apply { mCurrentPhoto = this })) } @@ -490,7 +499,7 @@ class ConversationFragment : BaseSupportFragment<ConversationPresenter, Conversa try { val photoFile = createImageFile(c) Log.i(TAG, "takePicture: trying to save to $photoFile") - val photoURI = getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, photoFile) + val photoURI = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, photoFile) val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).putExtra(MediaStore.EXTRA_OUTPUT, photoURI) .putExtra("android.intent.extras.CAMERA_FACING", 1) @@ -580,11 +589,7 @@ class ConversationFragment : BaseSupportFragment<ConversationPresenter, Conversa { Toast.makeText(context, R.string.generic_error, Toast.LENGTH_SHORT).show() }) } - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array<String>, - grantResults: IntArray - ) { + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { var i = 0 val n = permissions.size while (i < n) { @@ -669,7 +674,7 @@ class ConversationFragment : BaseSupportFragment<ConversationPresenter, Conversa val c = context ?: return var fileUri: Uri? = null try { - fileUri = getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path, displayName) + fileUri = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path, displayName) } catch (e: IllegalArgumentException) { Log.e("File Selector", "The selected file can't be shared: " + path.name) } @@ -689,7 +694,7 @@ class ConversationFragment : BaseSupportFragment<ConversationPresenter, Conversa val c = context ?: return var fileUri: Uri? = null try { - fileUri = getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path, displayName) + fileUri = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path, displayName) } catch (e: IllegalArgumentException) { Log.e(TAG, "The selected file can't be shared: " + path.name) } @@ -805,7 +810,7 @@ class ConversationFragment : BaseSupportFragment<ConversationPresenter, Conversa connection = object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { Log.w(TAG, "onServiceConnected") - val binder = service as LocalBinder + val binder = service as LocationSharingService.LocalBinder val locationService = binder.service //val path = ConversationPath(presenter.path) if (locationService.isSharing(path)) { diff --git a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java deleted file mode 100644 index f414197c3b812589c11fde5560f29f633dfebd2f..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * Adrien Béraud <adrien.beraud@savoirfairelinux.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 cx.ring.fragments; - -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.text.format.Formatter; -import android.view.inputmethod.EditorInfo; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.FragmentManager; -import androidx.preference.EditTextPreference; -import androidx.preference.Preference; -import androidx.preference.PreferenceManager; -import androidx.preference.SeekBarPreference; -import androidx.preference.SwitchPreference; -import androidx.preference.TwoStatePreference; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import cx.ring.R; -import cx.ring.account.AccountEditionFragment; -import cx.ring.application.JamiApplication; -import net.jami.model.Account; -import net.jami.model.AccountConfig; -import net.jami.model.ConfigKey; -import cx.ring.mvp.BasePreferenceFragment; -import cx.ring.services.SharedPreferencesServiceImpl; -import net.jami.utils.Log; -import net.jami.utils.Tuple; -import cx.ring.views.EditTextIntegerPreference; -import cx.ring.views.EditTextPreferenceDialog; -import cx.ring.views.PasswordPreference; -import dagger.hilt.android.AndroidEntryPoint; - -@AndroidEntryPoint -public class GeneralAccountFragment extends BasePreferenceFragment<GeneralAccountPresenter> implements GeneralAccountView { - - public static final String TAG = GeneralAccountFragment.class.getSimpleName(); - - private static final String DIALOG_FRAGMENT_TAG = "androidx.preference.PreferenceFragment.DIALOG"; - private final Preference.OnPreferenceChangeListener changeAccountStatusListener = (preference, newValue) -> { - presenter.setEnabled((Boolean) newValue); - return false; - }; - private final Preference.OnPreferenceChangeListener changeBasicPreferenceListener = (preference, newValue) -> { - Log.i(TAG, "Changing preference " + preference.getKey() + " to value:" + newValue); - final ConfigKey key = ConfigKey.fromString(preference.getKey()); - if (preference instanceof TwoStatePreference) { - presenter.twoStatePreferenceChanged(key, newValue); - } else if (preference instanceof PasswordPreference) { - StringBuilder tmp = new StringBuilder(); - for (int i = 0; i < ((String) newValue).length(); ++i) { - tmp.append("*"); - } - preference.setSummary(tmp.toString()); - presenter.passwordPreferenceChanged(key, newValue); - } else if (key == ConfigKey.ACCOUNT_USERNAME) { - presenter.userNameChanged(key, newValue); - preference.setSummary((CharSequence) newValue); - } else { - preference.setSummary((CharSequence) newValue); - presenter.preferenceChanged(key, newValue); - } - return true; - }; - - public static GeneralAccountFragment newInstance(@NonNull String accountId) { - Bundle bundle = new Bundle(); - bundle.putString(AccountEditionFragment.ACCOUNT_ID_KEY, accountId); - GeneralAccountFragment generalAccountFragment = new GeneralAccountFragment(); - generalAccountFragment.setArguments(bundle); - return generalAccountFragment; - } - - @Override - public void accountChanged(@NonNull Account account) { - PreferenceManager pm = getPreferenceManager(); - pm.setSharedPreferencesMode(Context.MODE_PRIVATE); - pm.setSharedPreferencesName(SharedPreferencesServiceImpl.PREFS_ACCOUNT+account.getAccountID()); - - setPreferenceDetails(account.getConfig()); - - SwitchPreference pref = findPreference("Account.status"); - if (account.isSip() && pref != null) { - String status; - pref.setTitle(account.getAlias()); - if (account.isEnabled()) { - if (account.isTrying()) { - status = getString(R.string.account_status_connecting); - } else if (account.needsMigration()) { - status = getString(R.string.account_update_needed); - } else if (account.isInError()) { - status = getString(R.string.account_status_connection_error); - } else if (account.isRegistered()) { - status = getString(R.string.account_status_online); - } else { - status = getString(R.string.account_status_unknown); - } - } else { - status = getString(R.string.account_status_offline); - } - pref.setSummary(status); - pref.setChecked(account.isEnabled()); - - // An ip2ip account is always ready - pref.setEnabled(!account.isIP2IP()); - - pref.setOnPreferenceChangeListener(changeAccountStatusListener); - } - - setPreferenceListener(account.getConfig(), changeBasicPreferenceListener); - } - - @Override - public void finish() { - Activity activity = getActivity(); - if (activity != null) - activity.onBackPressed(); - } - - @Override - public void updateResolutions(Tuple<Integer, Integer> maxResolution, int currentResolution) { - } - - private CharSequence getFileSizeSummary(int size, int maxSize) { - if (size == 0) { - return getText(R.string.account_accept_files_never); - } else if (size == maxSize) { - return getText(R.string.account_accept_files_always); - } else { - return Formatter.formatFileSize(requireContext(), size * 1000 * 1000); - } - } - - @Override - public void onCreatePreferences(Bundle bundle, String rootKey) { - super.onCreatePreferences(bundle, rootKey); - - Bundle args = getArguments(); - presenter.init(args == null ? null : args.getString(AccountEditionFragment.ACCOUNT_ID_KEY)); - - SeekBarPreference filePref = findPreference("acceptIncomingFilesMaxSize"); - if (filePref != null) { - filePref.setOnPreferenceChangeListener((p, v) -> { - SeekBarPreference pref = (SeekBarPreference)p; - p.setSummary(getFileSizeSummary((Integer) v, pref.getMax())); - return true; - }); - filePref.setSummary(getFileSizeSummary(filePref.getValue(), filePref.getMax())); - } - - Preference deletePref = findPreference("Account.delete"); - if (deletePref != null) { - deletePref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - AlertDialog deleteDialog = createDeleteDialog(); - deleteDialog.show(); - return false; - } - }); - } - } - - @Override - public void onDisplayPreferenceDialog(Preference preference) { - FragmentManager fragmentManager = getParentFragmentManager(); - if (fragmentManager.findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) { - return; - } - - if (preference instanceof EditTextIntegerPreference) { - EditTextPreferenceDialog f = EditTextPreferenceDialog.newInstance(preference.getKey(), EditorInfo.TYPE_CLASS_NUMBER); - f.setTargetFragment(this, 0); - f.show(fragmentManager, DIALOG_FRAGMENT_TAG); - } else if (preference instanceof PasswordPreference) { - EditTextPreferenceDialog f = EditTextPreferenceDialog.newInstance(preference.getKey(), EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD); - f.setTargetFragment(this, 0); - f.show(fragmentManager, DIALOG_FRAGMENT_TAG); - } else { - super.onDisplayPreferenceDialog(preference); - } - } - - private void setPreferenceDetails(AccountConfig details) { - for (ConfigKey confKey : details.getKeys()) { - Preference pref = findPreference(confKey.key()); - if (pref == null) { - continue; - } - if (!confKey.isTwoState()) { - String val = details.get(confKey); - ((EditTextPreference) pref).setText(val); - if (pref instanceof PasswordPreference) { - StringBuilder tmp = new StringBuilder(); - for (int i = 0; i < val.length(); ++i) { - tmp.append("*"); - } - pref.setSummary(tmp.toString()); - } else { - pref.setSummary(val); - } - } else { - ((TwoStatePreference) pref).setChecked(details.getBool(confKey)); - } - } - } - - private void setPreferenceListener(AccountConfig details, Preference.OnPreferenceChangeListener listener) { - for (ConfigKey confKey : details.getKeys()) { - Preference pref = findPreference(confKey.key()); - if (pref != null) { - pref.setOnPreferenceChangeListener(listener); - } - } - } - - @Override - public void addJamiPreferences(String accountId) { - PreferenceManager pm = getPreferenceManager(); - pm.setSharedPreferencesMode(Context.MODE_PRIVATE); - pm.setSharedPreferencesName(SharedPreferencesServiceImpl.PREFS_ACCOUNT+accountId); - addPreferencesFromResource(R.xml.account_prefs_jami); - } - - @Override - public void addSipPreferences() { - addPreferencesFromResource(R.xml.account_general_prefs); - } - - @NonNull - private AlertDialog createDeleteDialog() { - AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireContext()) - .setMessage(R.string.account_delete_dialog_message) - .setTitle(R.string.account_delete_dialog_title) - .setPositiveButton(R.string.menu_delete, (dialog, whichButton) -> presenter.removeAccount()) - .setNegativeButton(android.R.string.cancel, null) - .create(); - Activity activity = getActivity(); - if (activity != null) - alertDialog.setOwnerActivity(getActivity()); - return alertDialog; - } - -} diff --git a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..9bc313393f35ed1157ec1e80b8d3273a722b1fc4 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountFragment.kt @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Adrien Béraud <adrien.beraud@savoirfairelinux.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 cx.ring.fragments + +import android.app.Activity +import android.content.Context +import android.content.DialogInterface +import android.os.Bundle +import android.text.format.Formatter +import android.util.Log +import android.view.inputmethod.EditorInfo +import androidx.appcompat.app.AlertDialog +import androidx.preference.* +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import cx.ring.R +import cx.ring.account.AccountEditionFragment +import cx.ring.mvp.BasePreferenceFragment +import cx.ring.services.SharedPreferencesServiceImpl +import cx.ring.views.EditTextIntegerPreference +import cx.ring.views.EditTextPreferenceDialog +import cx.ring.views.PasswordPreference +import dagger.hilt.android.AndroidEntryPoint +import net.jami.model.Account +import net.jami.model.AccountConfig +import net.jami.model.ConfigKey +import net.jami.model.ConfigKey.Companion.fromString +import net.jami.settings.GeneralAccountPresenter +import net.jami.settings.GeneralAccountView +import net.jami.utils.Tuple + +@AndroidEntryPoint +class GeneralAccountFragment : BasePreferenceFragment<GeneralAccountPresenter>(), GeneralAccountView { + private val changeAccountStatusListener = Preference.OnPreferenceChangeListener { preference: Preference?, newValue: Any -> + presenter.setEnabled(newValue as Boolean) + false + } + private val changeBasicPreferenceListener = Preference.OnPreferenceChangeListener { preference: Preference, newValue: Any -> + Log.i(TAG, "Changing preference " + preference.key + " to value:" + newValue) + val key = fromString(preference.key) ?: return@OnPreferenceChangeListener false + if (preference is TwoStatePreference) { + presenter.twoStatePreferenceChanged(key, newValue) + } else if (preference is PasswordPreference) { + val tmp = StringBuilder() + var i = 0 + while (i < (newValue as String).length) { + tmp.append("*") + ++i + } + preference.setSummary(tmp.toString()) + presenter.passwordPreferenceChanged(key, newValue) + } else if (key === ConfigKey.ACCOUNT_USERNAME) { + presenter.userNameChanged(key, newValue) + preference.summary = newValue as CharSequence + } else { + preference.summary = newValue as CharSequence + presenter.preferenceChanged(key, newValue) + } + true + } + + override fun accountChanged(account: Account) { + val pm = preferenceManager + pm.sharedPreferencesMode = Context.MODE_PRIVATE + pm.sharedPreferencesName = SharedPreferencesServiceImpl.PREFS_ACCOUNT + account.accountID + setPreferenceDetails(account.config) + val pref = findPreference<SwitchPreference>("Account.status") + if (account.isSip && pref != null) { + pref.title = account.alias + val status: String = getString(if (account.isEnabled) { + when { + account.isTrying -> R.string.account_status_connecting + account.needsMigration() -> R.string.account_update_needed + account.isInError -> R.string.account_status_connection_error + account.isRegistered -> R.string.account_status_online + else -> R.string.account_status_unknown + } + } else { + R.string.account_status_offline + }) + pref.summary = status + pref.isChecked = account.isEnabled + + // An ip2ip account is always ready + pref.isEnabled = !account.isIP2IP + pref.onPreferenceChangeListener = changeAccountStatusListener + } + setPreferenceListener(account.config, changeBasicPreferenceListener) + } + + override fun finish() { + activity?.onBackPressed() + } + + override fun updateResolutions(maxResolution: Tuple<Int?, Int?>?, currentResolution: Int) {} + private fun getFileSizeSummary(size: Int, maxSize: Int): CharSequence { + return if (size == 0) { + getText(R.string.account_accept_files_never) + } else if (size == maxSize) { + getText(R.string.account_accept_files_always) + } else { + Formatter.formatFileSize(requireContext(), size * 1000L * 1000L) + } + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + super.onCreatePreferences(savedInstanceState, rootKey) + val args = arguments + presenter.init(args?.getString(AccountEditionFragment.ACCOUNT_ID_KEY)) + val filePref = findPreference<SeekBarPreference>("acceptIncomingFilesMaxSize") + if (filePref != null) { + filePref.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { p: Preference, v: Any -> + val pref = p as SeekBarPreference + p.setSummary(getFileSizeSummary(v as Int, pref.max)) + true + } + filePref.summary = getFileSizeSummary(filePref.value, filePref.max) + } + val deletePref = findPreference<Preference>("Account.delete") + if (deletePref != null) { + deletePref.onPreferenceClickListener = Preference.OnPreferenceClickListener { + val deleteDialog = createDeleteDialog() + deleteDialog.show() + false + } + } + } + + override fun onDisplayPreferenceDialog(preference: Preference) { + val fragmentManager = parentFragmentManager + if (fragmentManager.findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) { + return + } + if (preference is EditTextIntegerPreference) { + val f = EditTextPreferenceDialog.newInstance(preference.getKey(), EditorInfo.TYPE_CLASS_NUMBER) + f.setTargetFragment(this, 0) + f.show(fragmentManager, DIALOG_FRAGMENT_TAG) + } else if (preference is PasswordPreference) { + val f = EditTextPreferenceDialog.newInstance( + preference.getKey(), + EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_PASSWORD + ) + f.setTargetFragment(this, 0) + f.show(fragmentManager, DIALOG_FRAGMENT_TAG) + } else { + super.onDisplayPreferenceDialog(preference) + } + } + + private fun setPreferenceDetails(details: AccountConfig) { + for (confKey in details.keys) { + val pref = findPreference<Preference>(confKey.key()) ?: continue + if (!confKey.isTwoState) { + val `val` = details[confKey] + (pref as EditTextPreference).text = `val` + if (pref is PasswordPreference) { + val tmp = StringBuilder() + for (i in 0 until `val`.length) { + tmp.append("*") + } + pref.setSummary(tmp.toString()) + } else { + pref.setSummary(`val`) + } + } else { + (pref as TwoStatePreference).isChecked = details.getBool(confKey) + } + } + } + + private fun setPreferenceListener(details: AccountConfig, listener: Preference.OnPreferenceChangeListener) { + for (confKey in details.keys) { + val pref = findPreference<Preference>(confKey.key()) + if (pref != null) { + pref.onPreferenceChangeListener = listener + } + } + } + + override fun addJamiPreferences(accountId: String) { + val pm = preferenceManager + pm.sharedPreferencesMode = Context.MODE_PRIVATE + pm.sharedPreferencesName = SharedPreferencesServiceImpl.PREFS_ACCOUNT + accountId + addPreferencesFromResource(R.xml.account_prefs_jami) + } + + override fun addSipPreferences() { + addPreferencesFromResource(R.xml.account_general_prefs) + } + + private fun createDeleteDialog(): AlertDialog { + val alertDialog = MaterialAlertDialogBuilder(requireContext()) + .setMessage(R.string.account_delete_dialog_message) + .setTitle(R.string.account_delete_dialog_title) + .setPositiveButton(R.string.menu_delete) { dialog: DialogInterface?, whichButton: Int -> presenter.removeAccount() } + .setNegativeButton(android.R.string.cancel, null) + .create() + val activity: Activity? = activity + if (activity != null) alertDialog.setOwnerActivity(activity) + return alertDialog + } + + companion object { + val TAG = GeneralAccountFragment::class.simpleName!! + private const val DIALOG_FRAGMENT_TAG = "androidx.preference.PreferenceFragment.DIALOG" + fun newInstance(accountId: String): GeneralAccountFragment { + val bundle = Bundle() + bundle.putString(AccountEditionFragment.ACCOUNT_ID_KEY, accountId) + val generalAccountFragment = GeneralAccountFragment() + generalAccountFragment.arguments = bundle + return generalAccountFragment + } + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountPresenter.java b/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountPresenter.java deleted file mode 100644 index eb4b9b6accfcecc856b2baa6610271af8e901a65..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountPresenter.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.fragments; - -import android.util.Log; - -import javax.inject.Inject; -import javax.inject.Named; - -import net.jami.model.Account; -import net.jami.model.ConfigKey; -import net.jami.mvp.RootPresenter; -import net.jami.services.AccountService; -import net.jami.services.HardwareService; -import net.jami.services.PreferencesService; - -import io.reactivex.rxjava3.core.Scheduler; - -public class GeneralAccountPresenter extends RootPresenter<GeneralAccountView> { - - private static final String TAG = GeneralAccountPresenter.class.getSimpleName(); - - private final AccountService mAccountService; - private final HardwareService mHardwareService; - private final PreferencesService mPreferenceService; - - @Inject - @Named("UiScheduler") - protected Scheduler mUiScheduler; - - private Account mAccount; - - @Inject - GeneralAccountPresenter(AccountService accountService, HardwareService hardwareService, PreferencesService preferencesService) { - this.mAccountService = accountService; - this.mHardwareService = hardwareService; - this.mPreferenceService = preferencesService; - } - - // Init with current account - public void init() { - init(mAccountService.getCurrentAccount()); - } - - public void init(String accountId) { - init(mAccountService.getAccount(accountId)); - } - - private void init(Account account) { - mCompositeDisposable.clear(); - mAccount = account; - - if (account != null) { - if (account.isJami()) { - getView().addJamiPreferences(account.getAccountID()); - } else { - getView().addSipPreferences(); - } - getView().accountChanged(account); - mCompositeDisposable.add(mAccountService.getObservableAccount(account.getAccountID()) - .observeOn(mUiScheduler) - .subscribe(acc -> getView().accountChanged(acc))); - - mCompositeDisposable.add(mHardwareService.getMaxResolutions() - .observeOn(mUiScheduler) - .subscribe(res -> { - if (res.first == null) { - getView().updateResolutions(null, mPreferenceService.getResolution()); - } else { - getView().updateResolutions(res, mPreferenceService.getResolution()); - } - }, - e -> getView().updateResolutions(null, mPreferenceService.getResolution()))); - } else { - Log.e(TAG, "init: No currentAccount available"); - getView().finish(); - } - } - - void setEnabled(boolean enabled) { - mAccount.setEnabled(enabled); - mAccountService.setAccountEnabled(mAccount.getAccountID(), enabled); - } - - public void twoStatePreferenceChanged(ConfigKey configKey, Object newValue) { - if (configKey == ConfigKey.ACCOUNT_ENABLE) { - setEnabled((Boolean) newValue); - } else { - mAccount.setDetail(configKey, newValue.toString()); - updateAccount(); - } - } - - void passwordPreferenceChanged(ConfigKey configKey, Object newValue) { - if (mAccount.isSip()) { - mAccount.getCredentials().get(0).setDetail(configKey, newValue.toString()); - } - updateAccount(); - } - - void userNameChanged(ConfigKey configKey, Object newValue) { - if (mAccount.isSip()) { - mAccount.setDetail(configKey, newValue.toString()); - mAccount.getCredentials().get(0).setDetail(configKey, newValue.toString()); - } - updateAccount(); - } - - void preferenceChanged(ConfigKey configKey, Object newValue) { - mAccount.setDetail(configKey, newValue.toString()); - updateAccount(); - } - - private void updateAccount() { - mAccountService.setCredentials(mAccount.getAccountID(), mAccount.getCredentialsHashMapList()); - mAccountService.setAccountDetails(mAccount.getAccountID(), mAccount.getDetails()); - } - - public void removeAccount() { - mAccountService.removeAccount(mAccount.getAccountID()); - } - -} diff --git a/ring-android/app/src/main/java/cx/ring/fragments/LinkDeviceFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/LinkDeviceFragment.java index 5f167d250257f6703b30b8827de8fc1d30de825a..60420ddd3c3c88fbdc1085d652033aa1301f69a7 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/LinkDeviceFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/LinkDeviceFragment.java @@ -49,7 +49,6 @@ import net.jami.model.Account; import cx.ring.R; import cx.ring.account.AccountEditionFragment; -import cx.ring.application.JamiApplication; import cx.ring.databinding.FragLinkDeviceBinding; import cx.ring.mvp.BaseBottomSheetFragment; import cx.ring.utils.DeviceUtils; diff --git a/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferenceFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferenceFragment.java deleted file mode 100644 index e85f508d5c438a9be22c4fae2458d33d73fa4a8a..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferenceFragment.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> - * Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.fragments; - - -import android.content.Intent; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.preference.Preference; -import androidx.preference.TwoStatePreference; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import java.util.ArrayList; - -import cx.ring.R; -import cx.ring.account.AccountEditionFragment; -import cx.ring.application.JamiApplication; -import cx.ring.client.RingtoneActivity; -import net.jami.model.Account; -import net.jami.model.AccountConfig; -import net.jami.model.Codec; -import net.jami.model.ConfigKey; -import cx.ring.mvp.BasePreferenceFragment; -import dagger.hilt.android.AndroidEntryPoint; - -@AndroidEntryPoint -public class MediaPreferenceFragment extends BasePreferenceFragment<MediaPreferencePresenter> implements MediaPreferenceView { - - public static final String TAG = MediaPreferenceFragment.class.getSimpleName(); - private static final int SELECT_RINGTONE_PATH = 40; - private final Preference.OnPreferenceChangeListener changeVideoPreferenceListener = (preference, newValue) -> { - final ConfigKey key = ConfigKey.fromString(preference.getKey()); - presenter.videoPreferenceChanged(key, newValue); - return true; - }; - private CodecPreference audioCodecsPref = null; - private CodecPreference videoCodecsPref = null; - private final Preference.OnPreferenceChangeListener changeCodecListener = (preference, o) -> { - ArrayList<Long> audio = audioCodecsPref.getActiveCodecList(); - ArrayList<Long> video = videoCodecsPref.getActiveCodecList(); - ArrayList<Long> newOrder = new ArrayList<>(audio.size() + video.size()); - newOrder.addAll(audio); - newOrder.addAll(video); - presenter.codecChanged(newOrder); - return true; - }; - - public static MediaPreferenceFragment newInstance(@NonNull String accountId) { - Bundle bundle = new Bundle(); - bundle.putString(AccountEditionFragment.ACCOUNT_ID_KEY, accountId); - MediaPreferenceFragment mediaPreferenceFragment = new MediaPreferenceFragment(); - mediaPreferenceFragment.setArguments(bundle); - return mediaPreferenceFragment; - } - - @Override - public void onCreatePreferences(Bundle bundle, String rootKey) { - super.onCreatePreferences(bundle, rootKey); - - String accountId = getArguments().getString(AccountEditionFragment.ACCOUNT_ID_KEY); - - addPreferencesFromResource(R.xml.account_media_prefs); - audioCodecsPref = (CodecPreference) findPreference("Account.audioCodecs"); - videoCodecsPref = (CodecPreference) findPreference("Account.videoCodecs"); - Preference ringtonePref = findPreference("ringtone"); - ringtonePref.setOnPreferenceClickListener(preference -> { - Intent i = new Intent(requireActivity(), RingtoneActivity.class); - i.putExtra(AccountEditionFragment.ACCOUNT_ID_KEY, accountId); - requireActivity().startActivity(i); - return true; - }); - - presenter.init(accountId); - } - - @Override - public void accountChanged(Account account, ArrayList<Codec> audioCodec, ArrayList<Codec> videoCodec) { - if (account == null) { - return; - } - setPreferenceDetails(account.getConfig()); - audioCodecsPref.setCodecs(audioCodec); - videoCodecsPref.setCodecs(videoCodec); - - addPreferenceListener(ConfigKey.VIDEO_ENABLED, changeVideoPreferenceListener); - audioCodecsPref.setOnPreferenceChangeListener(changeCodecListener); - videoCodecsPref.setOnPreferenceChangeListener(changeCodecListener); - } - - @Override - public void displayWrongFileFormatDialog() { - new MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.ringtone_error_title) - .setMessage(R.string.ringtone_error_format_not_supported) - .setPositiveButton(android.R.string.ok, null) - .show(); - } - - @Override - public void displayPermissionCameraDenied() { - new MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.permission_dialog_camera_title) - .setMessage(R.string.permission_dialog_camera_message) - .setCancelable(false) - .setPositiveButton(android.R.string.ok, (dialog, which) -> dialog.dismiss()) - .show(); - } - - @Override - public void displayFileSearchDialog() { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("audio/*"); - startActivityForResult(intent, SELECT_RINGTONE_PATH); - } - - @Override - public void refresh(Account account) { - if (account != null) { - setPreferenceDetails(account.getConfig()); - } - if (null != getListView() && null != getListView().getAdapter()) { - getListView().getAdapter().notifyDataSetChanged(); - } - if (null != videoCodecsPref) { - videoCodecsPref.refresh(); - } - if (null != audioCodecsPref) { - audioCodecsPref.refresh(); - } - } - - - private void setPreferenceDetails(AccountConfig details) { - for (ConfigKey confKey : details.getKeys()) { - Preference pref = findPreference(confKey.key()); - - if (pref != null) { - if (pref instanceof TwoStatePreference) { - ((TwoStatePreference) pref).setChecked(details.getBool(confKey)); - } else if (confKey == ConfigKey.ACCOUNT_DTMF_TYPE) { - pref.setDefaultValue(details.get(confKey).contentEquals("overrtp") ? "RTP" : "SIP"); - pref.setSummary(details.get(confKey).contentEquals("overrtp") ? "RTP" : "SIP"); - } else { - pref.setSummary(details.get(confKey)); - } - } - } - } - - private void addPreferenceListener(AccountConfig details, Preference.OnPreferenceChangeListener listener) { - for (ConfigKey confKey : details.getKeys()) - addPreferenceListener(confKey, listener); - } - - private void addPreferenceListener(ConfigKey key, Preference.OnPreferenceChangeListener listener) { - Preference pref = findPreference(key.key()); - if (pref != null) { - pref.setOnPreferenceChangeListener(listener); - } - } - - -} diff --git a/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferenceFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferenceFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..b1b1843b2bfc3b9334f64b7e3b2fa5bc73136e7e --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferenceFragment.kt @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.fragments + +import android.content.DialogInterface +import android.content.Intent +import android.os.Bundle +import androidx.preference.Preference +import androidx.preference.TwoStatePreference +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import cx.ring.R +import cx.ring.account.AccountEditionFragment +import cx.ring.client.RingtoneActivity +import cx.ring.mvp.BasePreferenceFragment +import dagger.hilt.android.AndroidEntryPoint +import net.jami.model.Account +import net.jami.model.AccountConfig +import net.jami.model.Codec +import net.jami.model.ConfigKey +import net.jami.model.ConfigKey.Companion.fromString +import net.jami.settings.MediaPreferencePresenter +import net.jami.settings.MediaPreferenceView + +@AndroidEntryPoint +class MediaPreferenceFragment : BasePreferenceFragment<MediaPreferencePresenter>(), MediaPreferenceView { + private val changeVideoPreferenceListener = Preference.OnPreferenceChangeListener { preference: Preference, newValue: Any -> + val key = fromString(preference.key)!! + presenter.videoPreferenceChanged(key, newValue) + true + } + private var audioCodecsPref: CodecPreference? = null + private var videoCodecsPref: CodecPreference? = null + + private val changeCodecListener = Preference.OnPreferenceChangeListener { _, _ -> + val audio = audioCodecsPref!!.activeCodecList + val video = videoCodecsPref!!.activeCodecList + val newOrder = ArrayList<Long>(audio.size + video.size) + newOrder.addAll(audio) + newOrder.addAll(video) + presenter.codecChanged(newOrder) + true + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + super.onCreatePreferences(savedInstanceState, rootKey) + val accountId = requireArguments().getString(AccountEditionFragment.ACCOUNT_ID_KEY)!! + addPreferencesFromResource(R.xml.account_media_prefs) + audioCodecsPref = findPreference("Account.audioCodecs") + videoCodecsPref = findPreference("Account.videoCodecs") + findPreference<Preference>("ringtone")?.apply { + onPreferenceClickListener = Preference.OnPreferenceClickListener { + val i = Intent(requireActivity(), RingtoneActivity::class.java) + i.putExtra(AccountEditionFragment.ACCOUNT_ID_KEY, accountId) + requireActivity().startActivity(i) + true + } + } + presenter.init(accountId) + } + + override fun accountChanged(account: Account, audioCodec: ArrayList<Codec>, videoCodec: ArrayList<Codec>) { + setPreferenceDetails(account.config) + audioCodecsPref!!.setCodecs(audioCodec) + videoCodecsPref!!.setCodecs(videoCodec) + addPreferenceListener(ConfigKey.VIDEO_ENABLED, changeVideoPreferenceListener) + audioCodecsPref!!.onPreferenceChangeListener = changeCodecListener + videoCodecsPref!!.onPreferenceChangeListener = changeCodecListener + } + + override fun displayWrongFileFormatDialog() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.ringtone_error_title) + .setMessage(R.string.ringtone_error_format_not_supported) + .setPositiveButton(android.R.string.ok, null) + .show() + } + + override fun displayPermissionCameraDenied() { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.permission_dialog_camera_title) + .setMessage(R.string.permission_dialog_camera_message) + .setCancelable(false) + .setPositiveButton(android.R.string.ok) { dialog: DialogInterface, which: Int -> dialog.dismiss() } + .show() + } + + override fun displayFileSearchDialog() { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "audio/*" + startActivityForResult(intent, SELECT_RINGTONE_PATH) + } + + override fun refresh(account: Account) { + setPreferenceDetails(account.config) + if (null != listView && null != listView.adapter) { + listView.adapter!!.notifyDataSetChanged() + } + if (null != videoCodecsPref) { + videoCodecsPref!!.refresh() + } + if (null != audioCodecsPref) { + audioCodecsPref!!.refresh() + } + } + + private fun setPreferenceDetails(details: AccountConfig) { + for (confKey in details.keys) { + val pref = findPreference<Preference>(confKey.key()) + if (pref != null) { + if (pref is TwoStatePreference) { + pref.isChecked = details.getBool(confKey) + } else if (confKey === ConfigKey.ACCOUNT_DTMF_TYPE) { + pref.setDefaultValue(if (details[confKey].contentEquals("overrtp")) "RTP" else "SIP") + pref.summary = if (details[confKey].contentEquals("overrtp")) "RTP" else "SIP" + } else { + pref.summary = details[confKey] + } + } + } + } + + private fun addPreferenceListener(details: AccountConfig, listener: Preference.OnPreferenceChangeListener) { + for (confKey in details.keys) addPreferenceListener(confKey, listener) + } + + private fun addPreferenceListener(key: ConfigKey, listener: Preference.OnPreferenceChangeListener) { + findPreference<Preference>(key.key())?.onPreferenceChangeListener = listener + } + + companion object { + val TAG = MediaPreferenceFragment::class.simpleName!! + private const val SELECT_RINGTONE_PATH = 40 + fun newInstance(accountId: String): MediaPreferenceFragment { + val mediaPreferenceFragment = MediaPreferenceFragment() + mediaPreferenceFragment.arguments = Bundle().apply { putString(AccountEditionFragment.ACCOUNT_ID_KEY, accountId) } + return mediaPreferenceFragment + } + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferencePresenter.java b/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferencePresenter.java deleted file mode 100644 index 15085da4d8e9e6f5a73e00a6766790d80f086798..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferencePresenter.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.fragments; - -import android.util.Log; - -import java.util.ArrayList; - -import javax.inject.Inject; -import javax.inject.Named; - -import net.jami.model.Account; -import net.jami.model.Codec; -import net.jami.model.ConfigKey; -import net.jami.mvp.RootPresenter; -import net.jami.services.AccountService; -import net.jami.services.DeviceRuntimeService; - -import io.reactivex.rxjava3.core.Scheduler; - -public class MediaPreferencePresenter extends RootPresenter<MediaPreferenceView> -{ - public static final String TAG = MediaPreferencePresenter.class.getSimpleName(); - - protected AccountService mAccountService; - protected DeviceRuntimeService mDeviceRuntimeService; - - private Account mAccount; - - @Inject - @Named("UiScheduler") - protected Scheduler mUiScheduler; - - @Inject - public MediaPreferencePresenter(AccountService accountService, DeviceRuntimeService deviceRuntimeService) { - this.mAccountService = accountService; - this.mDeviceRuntimeService = deviceRuntimeService; - } - - @Override - public void unbindView() { - super.unbindView(); - } - - void init(String accountId) { - mAccount = mAccountService.getAccount(accountId); - mCompositeDisposable.clear(); - mCompositeDisposable.add(mAccountService - .getObservableAccount(accountId) - .switchMapSingle(account -> mAccountService.getCodecList(accountId) - .observeOn(mUiScheduler) - .doOnSuccess(codecList -> { - final ArrayList<Codec> audioCodec = new ArrayList<>(); - final ArrayList<Codec> videoCodec = new ArrayList<>(); - for (Codec codec : codecList) { - if (codec.getType() == Codec.Type.AUDIO) { - audioCodec.add(codec); - } else if (codec.getType() == Codec.Type.VIDEO) { - videoCodec.add(codec); - } - } - getView().accountChanged(account, audioCodec, videoCodec); - })) - .subscribe(l -> {}, e -> Log.e(TAG, "Error loading codec list"))); - } - - - void codecChanged(ArrayList<Long> codecs) { - mAccountService.setActiveCodecList(mAccount.getAccountID(), codecs); - } - - void videoPreferenceChanged(ConfigKey key, Object newValue) { - mAccount.setDetail(key, newValue.toString()); - updateAccount(); - } - - private void updateAccount() { - mAccountService.setCredentials(mAccount.getAccountID(), mAccount.getCredentialsHashMapList()); - mAccountService.setAccountDetails(mAccount.getAccountID(), mAccount.getDetails()); - } - -} diff --git a/ring-android/app/src/main/java/cx/ring/fragments/PluginHandlersListFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/PluginHandlersListFragment.kt index 85d83be18f4d1e447c71edcd26872d482606914d..53d449fc0c85f5f8b7caf6d8e2ed28ab3cff2b7d 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/PluginHandlersListFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/fragments/PluginHandlersListFragment.kt @@ -22,11 +22,7 @@ class PluginHandlersListFragment : Fragment(), PluginListItemListener { mPath = ConversationPath.fromBundle(requireArguments())!! } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { return FragPluginHandlersListBinding.inflate(inflater, container, false).also { b -> b.handlerList.setHasFixedSize(true) b.handlerList.adapter = PluginsListAdapter( @@ -52,15 +48,15 @@ class PluginHandlersListFragment : Fragment(), PluginListItemListener { } override fun onPluginItemClicked(pluginDetails: PluginDetails) { - JamiService.toggleChatHandler(pluginDetails.getmHandlerId(), mPath.accountId, mPath.conversationId, pluginDetails.isEnabled) + JamiService.toggleChatHandler(pluginDetails.handlerId, mPath.accountId, mPath.conversationId, pluginDetails.isEnabled) } override fun onPluginEnabled(pluginDetails: PluginDetails) { - JamiService.toggleChatHandler(pluginDetails.getmHandlerId(), mPath.accountId, mPath.conversationId, pluginDetails.isEnabled) + JamiService.toggleChatHandler(pluginDetails.handlerId, mPath.accountId, mPath.conversationId, pluginDetails.isEnabled) } companion object { - const val TAG = "PluginListHandlers" + val TAG = PluginHandlersListFragment::class.simpleName!! fun newInstance(accountId: String, peerId: String): PluginHandlersListFragment { val fragment = PluginHandlersListFragment() fragment.arguments = ConversationPath.toBundle(accountId, peerId) diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SIPAccountCreationFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/SIPAccountCreationFragment.kt index 8cf94046a5f673d9af196b3a46360f90339d8954..8b5942014dfa87271b428444d5aa0fc3d8bb3045 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/SIPAccountCreationFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/fragments/SIPAccountCreationFragment.kt @@ -36,8 +36,8 @@ import cx.ring.R import cx.ring.databinding.FragAccSipCreateBinding import cx.ring.mvp.BaseSupportFragment import dagger.hilt.android.AndroidEntryPoint -import net.jami.mvp.SIPCreationView -import net.jami.wizard.SIPCreationPresenter +import net.jami.account.SIPCreationView +import net.jami.account.SIPCreationPresenter @AndroidEntryPoint class SIPAccountCreationFragment : BaseSupportFragment<SIPCreationPresenter, SIPCreationView>(), @@ -77,10 +77,10 @@ class SIPAccountCreationFragment : BaseSupportFragment<SIPCreationPresenter, SIP private fun createSIPAccount(bypassWarnings: Boolean) { //orientation is locked during the create of account to avoid the destruction of the thread requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED - val hostname = binding!!.hostname.text.toString() - val proxy = binding!!.proxy.text.toString() - val username = binding!!.username.text.toString() - val password = binding!!.password.text.toString() + val hostname = binding!!.hostname.text?.toString() + val proxy = binding!!.proxy.text?.toString() + val username = binding!!.username.text?.toString() + val password = binding!!.password.text?.toString() presenter.startCreation(hostname, proxy, username, password, bypassWarnings) } diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.java deleted file mode 100644 index 0d4a11e6bbb2e6c4bdc59bc3342d0f95a09690ab..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.java +++ /dev/null @@ -1,317 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * Adrien Béraud <adrien.beraud@savoirfairelinux.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 cx.ring.fragments; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; - -import androidx.preference.EditTextPreference; -import androidx.preference.ListPreference; -import androidx.preference.Preference; -import androidx.preference.PreferenceCategory; -import androidx.preference.TwoStatePreference; - -import android.util.Pair; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.security.cert.CertificateFactory; -import java.util.ArrayList; -import java.util.Scanner; - -import cx.ring.R; -import cx.ring.account.AccountEditionFragment; -import cx.ring.application.JamiApplication; -import net.jami.model.AccountConfig; -import net.jami.model.AccountCredentials; -import net.jami.model.ConfigKey; -import cx.ring.mvp.BasePreferenceFragment; -import cx.ring.utils.AndroidFileUtils; -import net.jami.utils.Tuple; -import cx.ring.views.CredentialPreferenceDialog; -import cx.ring.views.CredentialsPreference; -import dagger.hilt.android.AndroidEntryPoint; - -@AndroidEntryPoint -public class SecurityAccountFragment extends BasePreferenceFragment<SecurityAccountPresenter> implements SecurityAccountView { - public static final String TAG = SecurityAccountFragment.class.getSimpleName(); - - private static final String DIALOG_FRAGMENT_TAG = "android.support.v14.preference.PreferenceFragment.DIALOG"; - private static final int SELECT_CA_LIST_RC = 42; - private static final int SELECT_PRIVATE_KEY_RC = 43; - private static final int SELECT_CERTIFICATE_RC = 44; - - private PreferenceCategory credentialsCategory; - private PreferenceCategory tlsCategory; - private final Preference.OnPreferenceChangeListener editCredentialListener = (preference, newValue) -> { - // We need the old and new value to correctly edit the list of credentials - Pair<AccountCredentials, AccountCredentials> result = (Pair<AccountCredentials, AccountCredentials>) newValue; - presenter.credentialEdited(new Tuple<>(result.first, result.second)); - return false; - }; - private final Preference.OnPreferenceChangeListener addCredentialListener = (preference, newValue) -> { - Pair<AccountCredentials, AccountCredentials> result = (Pair<AccountCredentials, AccountCredentials>) newValue; - presenter.credentialAdded(new Tuple<>(result.first, result.second)); - return false; - }; - private final Preference.OnPreferenceClickListener filePickerListener = preference -> { - if (preference.getKey().contentEquals(ConfigKey.TLS_CA_LIST_FILE.key())) { - performFileSearch(SELECT_CA_LIST_RC); - } - if (preference.getKey().contentEquals(ConfigKey.TLS_PRIVATE_KEY_FILE.key())) { - performFileSearch(SELECT_PRIVATE_KEY_RC); - } - if (preference.getKey().contentEquals(ConfigKey.TLS_CERTIFICATE_FILE.key())) { - performFileSearch(SELECT_CERTIFICATE_RC); - } - return true; - }; - private final Preference.OnPreferenceChangeListener tlsListener = (preference, newValue) -> { - ConfigKey key = ConfigKey.fromString(preference.getKey()); - - if (preference.getKey().contentEquals(ConfigKey.TLS_ENABLE.key())) { - if ((Boolean) newValue) { - presenter.tlsChanged(ConfigKey.STUN_ENABLE, false); - } - } - - if (preference.getKey().contentEquals(ConfigKey.SRTP_KEY_EXCHANGE.key())) { - newValue = ((Boolean) newValue) ? "sdes" : ""; - } - - if (!(preference instanceof TwoStatePreference)) { - preference.setSummary((String) newValue); - } - - presenter.tlsChanged(key, newValue); - - return true; - }; - - @Override - public void onCreatePreferences(Bundle bundle, String s) { - super.onCreatePreferences(bundle, s); - - addPreferencesFromResource(R.xml.account_security_prefs); - credentialsCategory = (PreferenceCategory) findPreference("Account.credentials"); - credentialsCategory.findPreference("Add.credentials").setOnPreferenceChangeListener(addCredentialListener); - tlsCategory = (PreferenceCategory) findPreference("TLS.category"); - - presenter.init(getArguments().getString(AccountEditionFragment.ACCOUNT_ID_KEY)); - } - - @Override - public void onDisplayPreferenceDialog(Preference preference) { - if (getFragmentManager().findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) { - return; - } - if (preference instanceof CredentialsPreference) { - CredentialPreferenceDialog preferenceDialog = CredentialPreferenceDialog.newInstance(preference.getKey()); - preferenceDialog.setTargetFragment(this, 0); - preferenceDialog.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); - } else { - super.onDisplayPreferenceDialog(preference); - } - } - - @Override - public void addAllCredentials(ArrayList<AccountCredentials> credentials) { - int i = 0; - for (AccountCredentials cred : credentials) { - CredentialsPreference toAdd = new CredentialsPreference(getPreferenceManager().getContext()); - toAdd.setKey("credential" + i); - toAdd.setPersistent(false); - toAdd.setCreds(cred); - toAdd.setIcon(null); - credentialsCategory.addPreference(toAdd); - i++; - toAdd.setOnPreferenceChangeListener(editCredentialListener); - } - } - - @Override - public void removeAllCredentials() { - int i = 0; - while (true) { - Preference toRemove = credentialsCategory.findPreference("credential" + i); - if (toRemove == null) { - break; - } - credentialsCategory.removePreference(toRemove); - i++; - } - } - - @Override - public void setDetails(AccountConfig config, String[] tlsMethods) { - for (int i = 0; i < tlsCategory.getPreferenceCount(); ++i) { - final Preference current = tlsCategory.getPreference(i); - final ConfigKey key = ConfigKey.fromString(current.getKey()); - - if (current instanceof TwoStatePreference) { - if (key == ConfigKey.SRTP_KEY_EXCHANGE) { - ((TwoStatePreference) current).setChecked(config.get(key).equals("sdes")); - } else { - ((TwoStatePreference) current).setChecked(config.getBool(key)); - } - } else { - if (key == ConfigKey.TLS_CA_LIST_FILE) { - File crt = new File(config.get(ConfigKey.TLS_CA_LIST_FILE)); - current.setSummary(crt.getName()); - setFeedbackIcon(current, crt); - current.setOnPreferenceClickListener(filePickerListener); - } else if (key == ConfigKey.TLS_PRIVATE_KEY_FILE) { - File pem = new File(config.get(ConfigKey.TLS_PRIVATE_KEY_FILE)); - current.setSummary(pem.getName()); - setFeedbackIcon(current, pem); - current.setOnPreferenceClickListener(filePickerListener); - } else if (key == ConfigKey.TLS_CERTIFICATE_FILE) { - File pem = new File(config.get(ConfigKey.TLS_CERTIFICATE_FILE)); - current.setSummary(pem.getName()); - setFeedbackIcon(current, pem); - checkForRSAKey(pem); - current.setOnPreferenceClickListener(filePickerListener); - } else if (key == ConfigKey.TLS_METHOD) { - ListPreference listPref = (ListPreference) current; - String curVal = config.get(key); - listPref.setEntries(tlsMethods); - listPref.setEntryValues(tlsMethods); - listPref.setValue(curVal); - current.setSummary(curVal); - } else if (current instanceof EditTextPreference) { - String val = config.get(key); - ((EditTextPreference) current).setText(val); - current.setSummary(val); - } else { - current.setSummary(config.get(key)); - } - } - - current.setOnPreferenceChangeListener(tlsListener); - } - } - - public boolean checkCertificate(File f) { - try { - FileInputStream fis = new FileInputStream(f.getAbsolutePath()); - CertificateFactory cf = CertificateFactory.getInstance("X509"); - cf.generateCertificate(fis); - return true; - } catch (Exception e) { - return false; - } - } - - public boolean findRSAKey(File f) { - // NOTE: This check is not complete but better than nothing. - try { - Scanner scanner = new Scanner(f); - while (scanner.hasNextLine()) { - if (scanner.nextLine().contains("-----BEGIN RSA PRIVATE KEY-----")) return true; - } - } catch(FileNotFoundException e) {} - return false; - } - - private void checkForRSAKey(File f) { - if (findRSAKey(f)) { - tlsCategory.findPreference(ConfigKey.TLS_PRIVATE_KEY_FILE.key()).setEnabled(false); - } else { - tlsCategory.findPreference(ConfigKey.TLS_PRIVATE_KEY_FILE.key()).setEnabled(true); - } - } - - private void setFeedbackIcon(Preference current, File certFile) { - Context c = current.getContext(); - boolean isKey = current.getKey().contentEquals(ConfigKey.TLS_PRIVATE_KEY_FILE.key()); - if (isKey && findRSAKey(certFile) || !isKey && checkCertificate(certFile)) { - Drawable icon = c.getDrawable(R.drawable.baseline_check_circle_24); - icon.setTint(c.getResources().getColor(R.color.green_500)); - current.setIcon(icon); - } else { - Drawable icon = c.getDrawable(R.drawable.baseline_error_24); - icon.setTint(c.getResources().getColor(R.color.colorError)); - current.setIcon(icon); - } - } - - public void performFileSearch(int requestCodeToSet) { - - // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file - // browser. - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - - // Filter to only show results that can be "opened", such as a - // file (as opposed to a list of contacts or timezones) - intent.addCategory(Intent.CATEGORY_OPENABLE); - - // Filter to show only images, using the image MIME data type. - // If one wanted to search for ogg vorbis files, the type would be "audio/ogg". - // To search for all documents available via installed storage providers, - // it would be "*/*". - intent.setType("*/*"); - startActivityForResult(intent, requestCodeToSet); - } - - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (resultCode == Activity.RESULT_CANCELED) { - return; - } - - Uri uri = data.getData(); - if (uri == null) { - return; - } - File myFile = new File(AndroidFileUtils.getRealPathFromURI(getContext(), uri)); - - ConfigKey key = null; - Preference preference; - switch (requestCode) { - case SELECT_CA_LIST_RC: - preference = tlsCategory.findPreference(ConfigKey.TLS_CA_LIST_FILE.key()); - preference.setSummary(myFile.getName()); - key = ConfigKey.TLS_CA_LIST_FILE; - setFeedbackIcon(preference, myFile); - break; - case SELECT_PRIVATE_KEY_RC: - preference = tlsCategory.findPreference(ConfigKey.TLS_PRIVATE_KEY_FILE.key()); - preference.setSummary(myFile.getName()); - key = ConfigKey.TLS_PRIVATE_KEY_FILE; - setFeedbackIcon(preference, myFile); - break; - case SELECT_CERTIFICATE_RC: - preference = tlsCategory.findPreference(ConfigKey.TLS_CERTIFICATE_FILE.key()); - preference.setSummary(myFile.getName()); - key = ConfigKey.TLS_CERTIFICATE_FILE; - setFeedbackIcon(preference, myFile); - checkForRSAKey(myFile); - break; - default: - break; - } - - presenter.fileActivityResult(key, myFile.getAbsolutePath()); - } -} diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..e8032c9d2e9031a15e362a6ccf550b7d339af326 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountFragment.kt @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Adrien Béraud <adrien.beraud@savoirfairelinux.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 cx.ring.fragments + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.util.Pair +import androidx.preference.* +import cx.ring.R +import cx.ring.account.AccountEditionFragment +import cx.ring.mvp.BasePreferenceFragment +import cx.ring.utils.AndroidFileUtils +import cx.ring.views.CredentialPreferenceDialog +import cx.ring.views.CredentialsPreference +import dagger.hilt.android.AndroidEntryPoint +import net.jami.account.SecurityAccountPresenter +import net.jami.account.SecurityAccountView +import net.jami.model.AccountConfig +import net.jami.model.AccountCredentials +import net.jami.model.ConfigKey +import java.io.File +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.security.cert.CertificateFactory +import java.util.* + +@AndroidEntryPoint +class SecurityAccountFragment : BasePreferenceFragment<SecurityAccountPresenter>(), SecurityAccountView { + private var credentialsCategory: PreferenceCategory? = null + private var tlsCategory: PreferenceCategory? = null + private val editCredentialListener = Preference.OnPreferenceChangeListener { preference: Preference?, newValue: Any -> + // We need the old and new value to correctly edit the list of credentials + val result = newValue as Pair<AccountCredentials, AccountCredentials> + presenter.credentialEdited(result.first, result.second) + false + } + private val addCredentialListener = Preference.OnPreferenceChangeListener { preference: Preference?, newValue: Any -> + val result = newValue as Pair<AccountCredentials, AccountCredentials> + presenter.credentialAdded(result.first, result.second) + false + } + private val filePickerListener = Preference.OnPreferenceClickListener { preference: Preference -> + if (preference.key.contentEquals(ConfigKey.TLS_CA_LIST_FILE.key())) { + performFileSearch(SELECT_CA_LIST_RC) + } + if (preference.key.contentEquals(ConfigKey.TLS_PRIVATE_KEY_FILE.key())) { + performFileSearch(SELECT_PRIVATE_KEY_RC) + } + if (preference.key.contentEquals(ConfigKey.TLS_CERTIFICATE_FILE.key())) { + performFileSearch(SELECT_CERTIFICATE_RC) + } + true + } + private val tlsListener = Preference.OnPreferenceChangeListener { preference: Preference, newValue: Any -> + var newValue = newValue + val key = ConfigKey.fromString(preference.key) + if (key == ConfigKey.TLS_ENABLE) { + if (newValue as Boolean) { + presenter.tlsChanged(ConfigKey.STUN_ENABLE, false) + } + } + if (key == ConfigKey.SRTP_KEY_EXCHANGE) { + newValue = if (newValue as Boolean) "sdes" else "" + } + if (preference !is TwoStatePreference) { + preference.summary = newValue as String + } + presenter.tlsChanged(key, newValue) + true + } + + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + super.onCreatePreferences(savedInstanceState, rootKey) + addPreferencesFromResource(R.xml.account_security_prefs) + credentialsCategory = findPreference<PreferenceCategory>("Account.credentials")?.apply { + findPreference<Preference>("Add.credentials")?.onPreferenceChangeListener = addCredentialListener + } + tlsCategory = findPreference("TLS.category") + presenter.init(requireArguments().getString(AccountEditionFragment.ACCOUNT_ID_KEY)!!) + } + + override fun onDisplayPreferenceDialog(preference: Preference) { + if (parentFragmentManager.findFragmentByTag(DIALOG_FRAGMENT_TAG) != null) { + return + } + if (preference is CredentialsPreference) { + val preferenceDialog = CredentialPreferenceDialog.newInstance(preference.getKey()) + preferenceDialog.setTargetFragment(this, 0) + preferenceDialog.show(parentFragmentManager, DIALOG_FRAGMENT_TAG) + } else { + super.onDisplayPreferenceDialog(preference) + } + } + + override fun addAllCredentials(credentials: List<AccountCredentials>) { + for ((i, cred) in credentials.withIndex()) { + val toAdd = CredentialsPreference(preferenceManager.context).apply { + key = "credential$i" + isPersistent = false + creds = cred + icon = null + } + credentialsCategory!!.addPreference(toAdd) + toAdd.onPreferenceChangeListener = editCredentialListener + } + } + + override fun removeAllCredentials() { + var i = 0 + while (true) { + val toRemove = credentialsCategory!!.findPreference<Preference>("credential$i") ?: break + credentialsCategory!!.removePreference(toRemove) + i++ + } + } + + override fun setDetails(config: AccountConfig, tlsMethods: Array<String>) { + for (i in 0 until tlsCategory!!.preferenceCount) { + val current = tlsCategory!!.getPreference(i) + val key = ConfigKey.fromString(current.key) + if (current is TwoStatePreference) { + if (key === ConfigKey.SRTP_KEY_EXCHANGE) { + current.isChecked = config[key] == "sdes" + } else { + current.isChecked = config.getBool(key!!) + } + } else { + if (key === ConfigKey.TLS_CA_LIST_FILE) { + val crt = File(config[ConfigKey.TLS_CA_LIST_FILE]) + current.summary = crt.name + setFeedbackIcon(current, crt) + current.onPreferenceClickListener = filePickerListener + } else if (key === ConfigKey.TLS_PRIVATE_KEY_FILE) { + val pem = File(config[ConfigKey.TLS_PRIVATE_KEY_FILE]) + current.summary = pem.name + setFeedbackIcon(current, pem) + current.onPreferenceClickListener = filePickerListener + } else if (key === ConfigKey.TLS_CERTIFICATE_FILE) { + val pem = File(config[ConfigKey.TLS_CERTIFICATE_FILE]) + current.summary = pem.name + setFeedbackIcon(current, pem) + checkForRSAKey(pem) + current.onPreferenceClickListener = filePickerListener + } else if (key === ConfigKey.TLS_METHOD) { + val listPref = current as ListPreference + val curVal = config[key] + listPref.entries = tlsMethods + listPref.entryValues = tlsMethods + listPref.value = curVal + current.setSummary(curVal) + } else if (current is EditTextPreference) { + val value = config[key!!] + current.text = value + current.setSummary(value) + } else { + current.summary = config[key!!] + } + } + current.onPreferenceChangeListener = tlsListener + } + } + + fun checkCertificate(f: File): Boolean { + return try { + val fis = FileInputStream(f.absolutePath) + val cf = CertificateFactory.getInstance("X509") + cf.generateCertificate(fis) + true + } catch (e: Exception) { + false + } + } + + fun findRSAKey(f: File?): Boolean { + // NOTE: This check is not complete but better than nothing. + try { + val scanner = Scanner(f) + while (scanner.hasNextLine()) { + if (scanner.nextLine().contains("-----BEGIN RSA PRIVATE KEY-----")) return true + } + } catch (e: FileNotFoundException) { + } + return false + } + + private fun checkForRSAKey(f: File) { + tlsCategory!!.findPreference<Preference>(ConfigKey.TLS_PRIVATE_KEY_FILE.key())!!.isEnabled = !findRSAKey(f) + } + + private fun setFeedbackIcon(current: Preference?, certFile: File) { + val c = current!!.context + val isKey = ConfigKey.TLS_PRIVATE_KEY_FILE.key() == current.key + if (isKey && findRSAKey(certFile) || !isKey && checkCertificate(certFile)) { + val icon = c.getDrawable(R.drawable.baseline_check_circle_24)!! + icon.setTint(c.resources.getColor(R.color.green_500)) + current.icon = icon + } else { + val icon = c.getDrawable(R.drawable.baseline_error_24)!! + icon.setTint(c.resources.getColor(R.color.colorError)) + current.icon = icon + } + } + + fun performFileSearch(requestCodeToSet: Int) { + + // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file + // browser. + val intent = Intent(Intent.ACTION_GET_CONTENT) + + // Filter to only show results that can be "opened", such as a + // file (as opposed to a list of contacts or timezones) + intent.addCategory(Intent.CATEGORY_OPENABLE) + + // Filter to show only images, using the image MIME data type. + // If one wanted to search for ogg vorbis files, the type would be "audio/ogg". + // To search for all documents available via installed storage providers, + // it would be "*/*". + intent.type = "*/*" + startActivityForResult(intent, requestCodeToSet) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (resultCode == Activity.RESULT_CANCELED) { + return + } + val uri = data!!.data ?: return + val myFile = File(AndroidFileUtils.getRealPathFromURI(requireContext(), uri)) + var key: ConfigKey? = null + val preference: Preference? + when (requestCode) { + SELECT_CA_LIST_RC -> { + preference = tlsCategory!!.findPreference(ConfigKey.TLS_CA_LIST_FILE.key()) + preference!!.summary = myFile.name + key = ConfigKey.TLS_CA_LIST_FILE + setFeedbackIcon(preference, myFile) + } + SELECT_PRIVATE_KEY_RC -> { + preference = tlsCategory!!.findPreference(ConfigKey.TLS_PRIVATE_KEY_FILE.key()) + preference!!.summary = myFile.name + key = ConfigKey.TLS_PRIVATE_KEY_FILE + setFeedbackIcon(preference, myFile) + } + SELECT_CERTIFICATE_RC -> { + preference = tlsCategory!!.findPreference(ConfigKey.TLS_CERTIFICATE_FILE.key()) + preference!!.summary = myFile.name + key = ConfigKey.TLS_CERTIFICATE_FILE + setFeedbackIcon(preference, myFile) + checkForRSAKey(myFile) + } + else -> { + } + } + presenter.fileActivityResult(key, myFile.absolutePath) + } + + companion object { + val TAG = SecurityAccountFragment::class.java.simpleName + private const val DIALOG_FRAGMENT_TAG = "androidx.preference.PreferenceFragment.DIALOG" + private const val SELECT_CA_LIST_RC = 42 + private const val SELECT_PRIVATE_KEY_RC = 43 + private const val SELECT_CERTIFICATE_RC = 44 + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountPresenter.java b/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountPresenter.java deleted file mode 100644 index 91be1c284a7f1c64887b43bc225ad4f2ba7dd003..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountPresenter.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.fragments; - -import java.util.List; - -import javax.inject.Inject; - -import net.jami.model.Account; -import net.jami.model.AccountCredentials; -import net.jami.model.ConfigKey; -import net.jami.mvp.RootPresenter; -import net.jami.services.AccountService; -import net.jami.utils.Tuple; - -public class SecurityAccountPresenter extends RootPresenter<SecurityAccountView> { - - protected AccountService mAccountService; - - private Account mAccount; - - @Inject - public SecurityAccountPresenter(AccountService accountService) { - this.mAccountService = accountService; - } - - public void init(String accountId) { - mAccount = mAccountService.getAccount(accountId); - if (mAccount != null) { - getView().removeAllCredentials(); - getView().addAllCredentials(mAccount.getCredentials()); - - List<String> methods = mAccountService.getTlsSupportedMethods(); - String[] tlsMethods = methods.toArray(new String[methods.size()]); - - getView().setDetails(mAccount.getConfig(), tlsMethods); - } - } - - public void credentialEdited(Tuple<AccountCredentials, AccountCredentials> result) { - mAccount.removeCredential(result.first); - if (result.second != null) { - // There is a new value for this credentials it means it has been edited (otherwise deleted) - mAccount.addCredential(result.second); - } - - mAccountService.setCredentials(mAccount.getAccountID(), mAccount.getCredentialsHashMapList()); - mAccountService.setAccountDetails(mAccount.getAccountID(), mAccount.getDetails()); - - getView().removeAllCredentials(); - getView().addAllCredentials(mAccount.getCredentials()); - } - - public void credentialAdded(Tuple<AccountCredentials, AccountCredentials> result) { - mAccount.addCredential(result.second); - - mAccountService.setCredentials(mAccount.getAccountID(), mAccount.getCredentialsHashMapList()); - mAccountService.setAccountDetails(mAccount.getAccountID(), mAccount.getDetails()); - - getView().removeAllCredentials(); - getView().addAllCredentials(mAccount.getCredentials()); - } - - public void tlsChanged(ConfigKey key, Object newValue) { - if (newValue instanceof Boolean) { - mAccount.setDetail(key, (Boolean) newValue); - } else { - mAccount.setDetail(key, (String) newValue); - } - - mAccountService.setCredentials(mAccount.getAccountID(), mAccount.getCredentialsHashMapList()); - mAccountService.setAccountDetails(mAccount.getAccountID(), mAccount.getDetails()); - } - - public void fileActivityResult(ConfigKey key, String filePath) { - mAccount.setDetail(key, filePath); - mAccountService.setCredentials(mAccount.getAccountID(), mAccount.getCredentialsHashMapList()); - mAccountService.setAccountDetails(mAccount.getAccountID(), mAccount.getDetails()); - } -} diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.kt index f8b53a6f6e2e0dbae98acf360b73da9228a1f160..12b1f22f57a4558f72b5e8393f6142422ecaab97 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/fragments/ShareWithFragment.kt @@ -62,20 +62,20 @@ class ShareWithFragment : Fragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { adapter = SmartListAdapter(null, object : SmartListListeners { - override fun onItemClick(smartListViewModel: SmartListViewModel) { + override fun onItemClick(item: SmartListViewModel) { mPendingIntent?.let { intent -> mPendingIntent = null val type = intent.type if (type != null && type.startsWith("text/")) { intent.putExtra(Intent.EXTRA_TEXT, binding!!.previewText.text.toString()) } - intent.putExtras(ConversationPath.toBundle(smartListViewModel.accountId, smartListViewModel.uri)) + intent.putExtras(ConversationPath.toBundle(item.accountId, item.uri)) intent.setClass(requireActivity(), ConversationActivity::class.java) startActivity(intent) } } - override fun onItemLongClick(smartListViewModel: SmartListViewModel) {} + override fun onItemLongClick(item: SmartListViewModel) {} }, mDisposable) val binding = FragSharewithBinding.inflate(inflater).apply { diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.kt b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.kt index d797f6f4641ba85f131ffdb71230422f04cfaaad..0b377baadeeedb951884246cb58badd0e07b14a2 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/fragments/SmartListFragment.kt @@ -233,8 +233,8 @@ class SmartListFragment : BaseSupportFragment<SmartListPresenter, SmartListView> presenter.removeConversation(conversationUri) } - override fun clearConversation(callContact: Uri) { - presenter.clearConversation(callContact) + override fun clearConversation(conversationUri: Uri) { + presenter.clearConversation(conversationUri) } override fun copyContactNumberToClipboard(contactNumber: String) { @@ -246,10 +246,6 @@ class SmartListFragment : BaseSupportFragment<SmartListPresenter, SmartListView> Snackbar.make(binding!!.listCoordinator, snackbarText, Snackbar.LENGTH_LONG).show() } - fun onFabButtonClicked() { - presenter.fabButtonClicked() - } - override fun displayChooseNumberDialog(numbers: Array<CharSequence>) { val context = requireContext() MaterialAlertDialogBuilder(context) @@ -297,12 +293,12 @@ class SmartListFragment : BaseSupportFragment<SmartListPresenter, SmartListView> } } - override fun displayClearDialog(uri: Uri) { - ActionHelper.launchClearAction(activity, uri, this@SmartListFragment) + override fun displayClearDialog(conversationUri: Uri) { + ActionHelper.launchClearAction(activity, conversationUri, this@SmartListFragment) } - override fun displayDeleteDialog(uri: Uri) { - ActionHelper.launchDeleteAction(activity, uri, this@SmartListFragment) + override fun displayDeleteDialog(conversationUri: Uri) { + ActionHelper.launchDeleteAction(activity, conversationUri, this@SmartListFragment) } override fun copyNumber(uri: Uri) { @@ -381,12 +377,12 @@ class SmartListFragment : BaseSupportFragment<SmartListPresenter, SmartListView> binding?.apply { confsList.scrollToPosition(0) } } - override fun onItemClick(smartListViewModel: SmartListViewModel) { - presenter.conversationClicked(smartListViewModel) + override fun onItemClick(item: SmartListViewModel) { + presenter.conversationClicked(item) } - override fun onItemLongClick(smartListViewModel: SmartListViewModel) { - presenter.conversationLongClicked(smartListViewModel) + override fun onItemLongClick(item: SmartListViewModel) { + presenter.conversationLongClicked(item) } private fun changeSeparatorHeight(open: Boolean) { diff --git a/ring-android/app/src/main/java/cx/ring/interfaces/BackHandlerInterface.java b/ring-android/app/src/main/java/cx/ring/interfaces/BackHandlerInterface.kt similarity index 88% rename from ring-android/app/src/main/java/cx/ring/interfaces/BackHandlerInterface.java rename to ring-android/app/src/main/java/cx/ring/interfaces/BackHandlerInterface.kt index e07eb5abfff3dc58bf369bca47ddee8f614678ec..64877e1214c3e5aeed89b4c854bdc3365923dd02 100644 --- a/ring-android/app/src/main/java/cx/ring/interfaces/BackHandlerInterface.java +++ b/ring-android/app/src/main/java/cx/ring/interfaces/BackHandlerInterface.kt @@ -16,8 +16,8 @@ * 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 cx.ring.interfaces; +package cx.ring.interfaces -public interface BackHandlerInterface { - boolean onBackPressed(); -} +interface BackHandlerInterface { + fun onBackPressed(): Boolean +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/interfaces/Colorable.java b/ring-android/app/src/main/java/cx/ring/interfaces/Colorable.java deleted file mode 100644 index eab2ea90c54e721b8435ba60aacfb5f8fa189c6a..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/interfaces/Colorable.java +++ /dev/null @@ -1,7 +0,0 @@ -package cx.ring.interfaces; - -import androidx.annotation.ColorInt; - -public interface Colorable { - void setColor(@ColorInt int color); -} diff --git a/ring-android/app/src/main/java/cx/ring/interfaces/Colorable.kt b/ring-android/app/src/main/java/cx/ring/interfaces/Colorable.kt new file mode 100644 index 0000000000000000000000000000000000000000..3b0adbf569f196564753ac4e15de05a65dc22231 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/interfaces/Colorable.kt @@ -0,0 +1,7 @@ +package cx.ring.interfaces + +import androidx.annotation.ColorInt + +interface Colorable { + fun setColor(@ColorInt color: Int) +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/mvp/BaseActivity.java b/ring-android/app/src/main/java/cx/ring/mvp/BaseActivity.java index 05954aa1f30f53301c1f21f431f85d88905d825e..ae3c9527f8bd424acab3fd1fd8714388261040e4 100644 --- a/ring-android/app/src/main/java/cx/ring/mvp/BaseActivity.java +++ b/ring-android/app/src/main/java/cx/ring/mvp/BaseActivity.java @@ -20,6 +20,8 @@ package cx.ring.mvp; import android.os.Bundle; + +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import net.jami.mvp.RootPresenter; @@ -31,7 +33,7 @@ public abstract class BaseActivity<T extends RootPresenter> extends AppCompatAct protected T presenter; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); presenter.bindView(this); diff --git a/ring-android/app/src/main/java/cx/ring/mvp/BaseBottomSheetFragment.java b/ring-android/app/src/main/java/cx/ring/mvp/BaseBottomSheetFragment.java index fe91a9472f22251d8b1086e3b7dff8fb66d70662..6cfb6df83830dd9b48cba827c9dab19c1107c72c 100644 --- a/ring-android/app/src/main/java/cx/ring/mvp/BaseBottomSheetFragment.java +++ b/ring-android/app/src/main/java/cx/ring/mvp/BaseBottomSheetFragment.java @@ -21,10 +21,10 @@ package cx.ring.mvp; import android.os.Bundle; import android.view.View; -import android.widget.Toast; import androidx.annotation.IdRes; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; @@ -32,11 +32,9 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import javax.inject.Inject; import cx.ring.R; -import net.jami.model.Error; -import net.jami.mvp.BaseView; import net.jami.mvp.RootPresenter; -public abstract class BaseBottomSheetFragment<T extends RootPresenter> extends BottomSheetDialogFragment implements BaseView { +public abstract class BaseBottomSheetFragment<T extends RootPresenter> extends BottomSheetDialogFragment { protected static final String TAG = BaseBottomSheetFragment.class.getSimpleName(); @@ -44,7 +42,7 @@ public abstract class BaseBottomSheetFragment<T extends RootPresenter> extends B protected T presenter; @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { //Be sure to do the injection in onCreateView method if (presenter != null) { presenter.bindView(this); @@ -59,34 +57,11 @@ public abstract class BaseBottomSheetFragment<T extends RootPresenter> extends B presenter.unbindView(); } - public void displayErrorToast(Error error) { - String errorString; - switch (error) { - case NO_INPUT: - errorString = getString(R.string.call_error_no_camera_no_microphone); - break; - case INVALID_FILE: - errorString = getString(R.string.invalid_file); - break; - case NOT_ABLE_TO_WRITE_FILE: - errorString = getString(R.string.not_able_to_write_file); - break; - case NO_SPACE_LEFT: - errorString = getString(R.string.no_space_left_on_device); - break; - default: - errorString = getString(R.string.generic_error); - break; - } - - Toast.makeText(requireContext(), errorString, Toast.LENGTH_LONG).show(); - } - protected void initPresenter(T presenter) { } protected void replaceFragmentWithSlide(Fragment fragment, @IdRes int content) { - getFragmentManager() + getParentFragmentManager() .beginTransaction() .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right) @@ -96,7 +71,7 @@ public abstract class BaseBottomSheetFragment<T extends RootPresenter> extends B } protected void replaceFragment(Fragment fragment, @IdRes int content) { - getFragmentManager() + getParentFragmentManager() .beginTransaction() .replace(content, fragment, TAG) .addToBackStack(TAG) diff --git a/ring-android/app/src/main/java/cx/ring/mvp/BasePreferenceFragment.java b/ring-android/app/src/main/java/cx/ring/mvp/BasePreferenceFragment.java index 60e3749d0d0f77a532d23dc5daebbd42859299fd..7d783d456ead0623dbb839ceea082f8927b239ec 100644 --- a/ring-android/app/src/main/java/cx/ring/mvp/BasePreferenceFragment.java +++ b/ring-android/app/src/main/java/cx/ring/mvp/BasePreferenceFragment.java @@ -21,20 +21,19 @@ package cx.ring.mvp; import android.os.Bundle; +import androidx.annotation.Nullable; import androidx.preference.PreferenceFragmentCompat; import net.jami.mvp.RootPresenter; import javax.inject.Inject; -import dagger.hilt.android.AndroidEntryPoint; - public abstract class BasePreferenceFragment<T extends RootPresenter> extends PreferenceFragmentCompat { @Inject protected T presenter; @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + public void onCreatePreferences(@Nullable Bundle savedInstanceState, @Nullable String rootKey) { //Be sure to do the injection in onCreateView method presenter.bindView(this); } diff --git a/ring-android/app/src/main/java/cx/ring/mvp/BaseSupportFragment.kt b/ring-android/app/src/main/java/cx/ring/mvp/BaseSupportFragment.kt index 752b8b5b02e56bbc12dafee335ed91a5ec6b6ef7..5b52dc8af85e058c1eb0d8b4fac1e4681bf65b53 100644 --- a/ring-android/app/src/main/java/cx/ring/mvp/BaseSupportFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/mvp/BaseSupportFragment.kt @@ -21,16 +21,13 @@ package cx.ring.mvp import android.os.Bundle import android.view.View -import android.widget.Toast import androidx.annotation.IdRes import androidx.fragment.app.Fragment import cx.ring.R -import net.jami.model.Error -import net.jami.mvp.BaseView import net.jami.mvp.RootPresenter import javax.inject.Inject -abstract class BaseSupportFragment<T : RootPresenter<V>, V> : Fragment(), BaseView { +abstract class BaseSupportFragment<T : RootPresenter<in V>, in V> : Fragment() { @Inject lateinit var presenter: T @@ -50,40 +47,26 @@ abstract class BaseSupportFragment<T : RootPresenter<V>, V> : Fragment(), BaseVi presenter.onDestroy() } - override fun displayErrorToast(error: Error) { - val errorString: String = when (error) { - Error.NO_INPUT -> getString(R.string.call_error_no_camera_no_microphone) - Error.INVALID_FILE -> getString(R.string.invalid_file) - Error.NOT_ABLE_TO_WRITE_FILE -> getString(R.string.not_able_to_write_file) - Error.NO_SPACE_LEFT -> getString(R.string.no_space_left_on_device) - else -> getString(R.string.generic_error) - } - Toast.makeText(requireContext(), errorString, Toast.LENGTH_LONG).show() - } - protected open fun initPresenter(presenter: T) {} - protected fun replaceFragmentWithSlide(fragment: Fragment?, @IdRes content: Int) { + protected fun replaceFragmentWithSlide(fragment: Fragment, @IdRes content: Int) { parentFragmentManager .beginTransaction() - .setCustomAnimations( - R.anim.slide_in_right, - R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right - ) - .replace(content, fragment!!, TAG) + .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right) + .replace(content, fragment, TAG) .addToBackStack(TAG) .commit() } - protected fun replaceFragment(fragment: Fragment?, @IdRes content: Int) { + protected fun replaceFragment(fragment: Fragment, @IdRes content: Int) { parentFragmentManager .beginTransaction() - .replace(content, fragment!!, TAG) + .replace(content, fragment, TAG) .addToBackStack(TAG) .commit() } companion object { - protected val TAG = BaseSupportFragment::class.simpleName + protected val TAG = BaseSupportFragment::class.simpleName!! } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.java b/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.java deleted file mode 100644 index f5eb793cd8197b8c2ef20620550c3c18974da1b0..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.java +++ /dev/null @@ -1,187 +0,0 @@ -package cx.ring.plugins; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import cx.ring.settings.pluginssettings.PluginDetails; - -import net.jami.daemon.JamiService; -import net.jami.daemon.StringMap; -import net.jami.utils.Log; - -public class PluginUtils { - - public static final String TAG = PluginUtils.class.getSimpleName(); - - /** - * Fetches the plugins folder in the internal storage for plugins subfolder - * Gathers the details of each plugin in a PluginDetails instance - * @param mContext The current context - * @return List of PluginDetails - */ - public static List<PluginDetails> getInstalledPlugins(Context mContext){ - tree(mContext.getFilesDir() + File.separator+ "plugins",0); - tree(mContext.getCacheDir().getAbsolutePath(),0); - - List<String> pluginsPaths = JamiService.getInstalledPlugins(); - List<String> loadedPluginsPaths = JamiService.getLoadedPlugins(); - - List<PluginDetails> pluginsList = new ArrayList<>(pluginsPaths.size()); - for (String pluginPath : pluginsPaths) { - File pluginFolder = new File(pluginPath); - if(pluginFolder.isDirectory()) { - pluginsList.add(new PluginDetails( - pluginFolder.getName(), - pluginFolder.getAbsolutePath(), loadedPluginsPaths.contains(pluginPath))); - } - } - return pluginsList; - } - - /** - * Fetches the plugins folder in the internal storage for plugins subfolder - * Gathers the details of each plugin in a PluginDetails instance - * @param mContext The current context - * @param accountId The current account id - * @param peerId The current conversation peer id - * @return List of PluginDetails - */ - public static List<PluginDetails> getChatHandlersDetails(Context mContext, String accountId, String peerId){ - tree(mContext.getFilesDir() + File.separator+ "plugins",0); - tree(mContext.getCacheDir().getAbsolutePath(),0); - - List<String> chatHandlersId = JamiService.getChatHandlers(); - List<String> chatHandlerStatus = JamiService.getChatHandlerStatus(accountId, peerId); - - List<PluginDetails> handlersList = new ArrayList<>(chatHandlersId.size()); - for (String handlerId : chatHandlersId) { - StringMap handlerDetails = JamiService.getChatHandlerDetails(handlerId); - String pluginPath = handlerDetails.get("pluginId"); - pluginPath = pluginPath.substring(0, pluginPath.lastIndexOf("/data")); - boolean enabled = false; - - if (chatHandlerStatus.contains(handlerId)) { - enabled = true; - } - handlersList.add(new PluginDetails( - handlerDetails.get("name"), - pluginPath, enabled, handlerId)); - } - return handlersList; - } - - /** - * Loads the so file and instantiates the plugin init function (toggle on) - * @param path root path of the plugin - * @return true if loaded - */ - public static boolean loadPlugin(String path) { - return JamiService.loadPlugin(path); - } - - /** - * Toggles the plugin off (destroying any objects created by the plugin) - * then unloads the so file - * @param path root path of the plugin - * @return true if unloaded - */ - public static boolean unloadPlugin(String path) { - return JamiService.unloadPlugin(path); - } - - /** - * Lists the root paths of the loaded plugins - * @return list of path - */ - public static List<String> getLoadedPlugins() { - return JamiService.getLoadedPlugins(); - } - - /** - * Displays the content of any directory - * @param dirPath directory to display - * @param level default 0, exists because the function is recursive - */ - public static void tree(String dirPath, int level) { - String repeated = new String(new char[level]).replace("\0", "\t|"); - File file = new File(dirPath); - if(file.exists()) { - Log.d(TAG, "|"+ repeated + "-- " + file.getName()); - if(file.isDirectory()) { - File[] files = file.listFiles(); - if (files != null && files.length > 0) { - for(File f : files) { - tree(f.getAbsolutePath(),level+1); - } - } - } - } - } - - /** - * Useful Util method that is available in for android api >= 24 - * We emulate it here - * - * @param input input object that can be null - * @param defaultValue default NonNull object of the same type as input - * @return input if not null, defaultValue otherwise - */ - public static <T> T getOrElse(T input, @NonNull T defaultValue) { - if (input == null) { - return defaultValue; - } else { - return input; - } - } - - /** - * - * @param listString List<String> - * @return String of the form String entries = "[AAA,BBB,CCC]" - */ - public static String listStringToStringList(List<String> listString) { - StringBuilder stringBuilder = new StringBuilder(); - - if(!listString.isEmpty()) { - for(int i=0; i< listString.size(); i++){ - stringBuilder.append(listString.get(i)).append(","); - } - stringBuilder.append(listString.get(listString.size())); - } - - return stringBuilder.toString(); - } - - /** - * Converts a string that contains a list to a java List<String> - * E.g: String entries = "[AAA,BBB,CCC]" to List<String> l, where l.get(0) = "AAA" - * @return List of strings - * @param stringList a string in the form "[AAA,BBB,CCC]" - */ - public static List<String> stringListToListString(String stringList) { - List<String> listString = new ArrayList<>(); - StringBuilder currentWord = new StringBuilder(); - if(!stringList.isEmpty()) { - for (int i = 0; i < stringList.length(); i++) { - char currentChar = stringList.charAt(i); - if (currentChar != ',') { - currentWord.append(currentChar); - } else { - listString.add(currentWord.toString()); - currentWord = new StringBuilder(); - } - - if (i == stringList.length() - 1) { - listString.add(currentWord.toString()); - break; - } - } - } - return listString; - } -} diff --git a/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.kt b/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..553065f119c063cb649bd5d97079f7dd26997c17 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/plugins/PluginUtils.kt @@ -0,0 +1,140 @@ +package cx.ring.plugins + +import android.content.Context +import android.util.Log +import cx.ring.settings.pluginssettings.PluginDetails +import cx.ring.plugins.PluginUtils +import net.jami.daemon.JamiService +import net.jami.daemon.StringMap +import java.io.File +import java.lang.StringBuilder +import java.util.ArrayList + +object PluginUtils { + val TAG = PluginUtils::class.simpleName!! + + /** + * Fetches the plugins folder in the internal storage for plugins subfolder + * Gathers the details of each plugin in a PluginDetails instance + * @param mContext The current context + * @return List of PluginDetails + */ + @JvmStatic + fun getInstalledPlugins(mContext: Context): List<PluginDetails> { + tree(mContext.filesDir.toString() + File.separator + "plugins", 0) + tree(mContext.cacheDir.absolutePath, 0) + val pluginsPaths: List<String> = JamiService.getInstalledPlugins() + val loadedPluginsPaths: List<String> = JamiService.getLoadedPlugins() + val pluginsList: MutableList<PluginDetails> = ArrayList(pluginsPaths.size) + for (pluginPath in pluginsPaths) { + val pluginFolder = File(pluginPath) + if (pluginFolder.isDirectory) { + pluginsList.add( + PluginDetails( + pluginFolder.name, + pluginFolder.absolutePath, + loadedPluginsPaths.contains(pluginPath) + ) + ) + } + } + return pluginsList + } + + /** + * Fetches the plugins folder in the internal storage for plugins subfolder + * Gathers the details of each plugin in a PluginDetails instance + * @param mContext The current context + * @param accountId The current account id + * @param peerId The current conversation peer id + * @return List of PluginDetails + */ + fun getChatHandlersDetails(mContext: Context, accountId: String, peerId: String): List<PluginDetails> { + tree(mContext.filesDir.toString() + File.separator + "plugins", 0) + tree(mContext.cacheDir.absolutePath, 0) + val chatHandlersId: List<String> = JamiService.getChatHandlers() + val chatHandlerStatus: List<String> = JamiService.getChatHandlerStatus(accountId, peerId) + val handlersList: MutableList<PluginDetails> = ArrayList(chatHandlersId.size) + for (handlerId in chatHandlersId) { + val handlerDetails = JamiService.getChatHandlerDetails(handlerId) + var pluginPath = handlerDetails["pluginId"] + pluginPath = pluginPath!!.substring(0, pluginPath.lastIndexOf("/data")) + var enabled = false + if (chatHandlerStatus.contains(handlerId)) { + enabled = true + } + handlersList.add(PluginDetails(handlerDetails["name"]!!, pluginPath, enabled, handlerId)) + } + return handlersList + } + + /** + * Loads the so file and instantiates the plugin init function (toggle on) + * @param path root path of the plugin + * @return true if loaded + */ + @JvmStatic + fun loadPlugin(path: String?): Boolean { + return JamiService.loadPlugin(path) + } + + /** + * Toggles the plugin off (destroying any objects created by the plugin) + * then unloads the so file + * @param path root path of the plugin + * @return true if unloaded + */ + @JvmStatic + fun unloadPlugin(path: String?): Boolean { + return JamiService.unloadPlugin(path) + } + + /** + * Displays the content of any directory + * @param dirPath directory to display + * @param level default 0, exists because the function is recursive + */ + fun tree(dirPath: String, level: Int) { + val repeated = String(CharArray(level)).replace("\u0000", "\t|") + val file = File(dirPath) + if (file.exists()) { + Log.d(TAG, "|" + repeated + "-- " + file.name) + if (file.isDirectory) { + val files = file.listFiles() + if (files != null && files.isNotEmpty()) { + for (f in files) { + tree(f.absolutePath, level + 1) + } + } + } + } + } + + /** + * Converts a string that contains a list to a java List<String> + * E.g: String entries = "[AAA,BBB,CCC]" to List<String> l, where l.get(0) = "AAA" + * @return List of strings + * @param stringList a string in the form "[AAA,BBB,CCC]" + </String></String> */ + @JvmStatic + fun stringListToListString(stringList: String): List<String> { + val listString: MutableList<String> = ArrayList() + val currentWord = StringBuilder() + if (stringList.isNotEmpty()) { + for (i in stringList.indices) { + val currentChar = stringList[i] + if (currentChar != ',') { + currentWord.append(currentChar) + } else { + listString.add(currentWord.toString()) + currentWord.clear() + } + if (i == stringList.length - 1) { + listString.add(currentWord.toString()) + break + } + } + } + return listString + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerLayoutManager.java b/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerLayoutManager.java deleted file mode 100644 index 60d66b076e58ea8f480e4ce50cca1c80ca4b7abd..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerLayoutManager.java +++ /dev/null @@ -1,100 +0,0 @@ -package cx.ring.plugins.RecyclerPicker; - -import android.content.Context; -import android.view.View; - -import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.LinearSnapHelper; -import androidx.recyclerview.widget.RecyclerView; - -public class RecyclerPickerLayoutManager extends LinearLayoutManager { - private RecyclerView recyclerView; - private final ItemSelectedListener listener; - - public RecyclerPickerLayoutManager(Context context, int orientation, boolean reverseLayout, ItemSelectedListener listener) { - super(context, orientation, reverseLayout); - this.listener = listener; - } - - @Override - public void onAttachedToWindow(RecyclerView view) { - super.onAttachedToWindow(view); - recyclerView = view; - - // Smart snapping - LinearSnapHelper snapHelper = new LinearSnapHelper(); - snapHelper.attachToRecyclerView(recyclerView); - } - - @Override - public void onLayoutCompleted(RecyclerView.State state) { - super.onLayoutCompleted(state); - scaleDownView(); - } - - - @Override - public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, - RecyclerView.State state) { - int scrolled = super.scrollHorizontallyBy(dx, recycler, state); - - if (getOrientation() == VERTICAL) { - return 0; - } else { - scaleDownView(); - return scrolled; - } - } - - @Override - public void onScrollStateChanged(int state) { - super.onScrollStateChanged(state); - // When scroll stops we notify on the selected item - if (state == RecyclerView.SCROLL_STATE_IDLE) { - - // Find the closest child to the recyclerView center --> this is the selected item. - int recyclerViewCenterX = getRecyclerViewCenterX(); - int minDistance = recyclerView.getWidth(); - int position = -1; - for (int i=0; i< recyclerView.getChildCount(); i++) { - View child = recyclerView.getChildAt(i); - int childCenterX = getDecoratedLeft(child) + (getDecoratedRight(child) - getDecoratedLeft(child)) / 2; - int newDistance = Math.abs(childCenterX - recyclerViewCenterX); - if (newDistance < minDistance) { - minDistance = newDistance; - position = recyclerView.getChildLayoutPosition(child); - } - } - listener.onItemSelected(position); - } - } - - private int getRecyclerViewCenterX() { - return recyclerView.getWidth()/2 + recyclerView.getLeft(); - } - - private void scaleDownView() { - float mid = getWidth() / 2.0f; - for (int i=0; i<getChildCount(); i++) { - - // Calculating the distance of the child from the center - View child = getChildAt(i); - float childMid = (getDecoratedLeft(child) + getDecoratedRight(child)) / 2.0f; - float distanceFromCenter = Math.abs(mid - childMid); - - // The scaling formula - float k = (float) Math.sqrt((double)(distanceFromCenter/getWidth())); - k *= 1.5f; - float scale = 1-k*0.66f; - - // Set scale to view - child.setScaleX(scale); - child.setScaleY(scale); - } - } - - public interface ItemSelectedListener { - void onItemSelected(int position); - void onItemClicked(int position); - } -} diff --git a/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerLayoutManager.kt b/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerLayoutManager.kt new file mode 100644 index 0000000000000000000000000000000000000000..757aecb4b458764a4c0c74a4ae0d960a73d9810e --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/plugins/RecyclerPicker/RecyclerPickerLayoutManager.kt @@ -0,0 +1,92 @@ +package cx.ring.plugins.RecyclerPicker + +import android.content.Context +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.LinearSnapHelper +import androidx.recyclerview.widget.RecyclerView.Recycler + +class RecyclerPickerLayoutManager( + context: Context?, + orientation: Int, + reverseLayout: Boolean, + private val listener: ItemSelectedListener +) : LinearLayoutManager(context, orientation, reverseLayout) { + private var recyclerView: RecyclerView? = null + override fun onAttachedToWindow(view: RecyclerView) { + super.onAttachedToWindow(view) + recyclerView = view + + // Smart snapping + val snapHelper = LinearSnapHelper() + snapHelper.attachToRecyclerView(recyclerView) + } + + override fun onLayoutCompleted(state: RecyclerView.State) { + super.onLayoutCompleted(state) + scaleDownView() + } + + override fun scrollHorizontallyBy( + dx: Int, recycler: Recycler, + state: RecyclerView.State + ): Int { + val scrolled = super.scrollHorizontallyBy(dx, recycler, state) + return if (orientation == VERTICAL) { + 0 + } else { + scaleDownView() + scrolled + } + } + + override fun onScrollStateChanged(state: Int) { + super.onScrollStateChanged(state) + // When scroll stops we notify on the selected item + if (state == RecyclerView.SCROLL_STATE_IDLE) { + + // Find the closest child to the recyclerView center --> this is the selected item. + val recyclerViewCenterX = recyclerViewCenterX + var minDistance = recyclerView!!.width + var position = -1 + for (i in 0 until recyclerView!!.childCount) { + val child = recyclerView!!.getChildAt(i) + val childCenterX = getDecoratedLeft(child) + (getDecoratedRight(child) - getDecoratedLeft(child)) / 2 + val newDistance = Math.abs(childCenterX - recyclerViewCenterX) + if (newDistance < minDistance) { + minDistance = newDistance + position = recyclerView!!.getChildLayoutPosition(child) + } + } + listener.onItemSelected(position) + } + } + + private val recyclerViewCenterX: Int + private get() = recyclerView!!.width / 2 + recyclerView!!.left + + private fun scaleDownView() { + val mid = width / 2.0f + for (i in 0 until childCount) { + + // Calculating the distance of the child from the center + val child = getChildAt(i) + val childMid = (getDecoratedLeft(child!!) + getDecoratedRight(child)) / 2.0f + val distanceFromCenter = Math.abs(mid - childMid) + + // The scaling formula + var k = Math.sqrt((distanceFromCenter / width).toDouble()).toFloat() + k *= 1.5f + val scale = 1 - k * 0.66f + + // Set scale to view + child.scaleX = scale + child.scaleY = scale + } + } + + interface ItemSelectedListener { + fun onItemSelected(position: Int) + fun onItemClicked(position: Int) + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/service/BootReceiver.java b/ring-android/app/src/main/java/cx/ring/service/BootReceiver.java deleted file mode 100644 index 40cc0153b5b0ba43e0ef53abb57cde35075adbf2..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/service/BootReceiver.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * 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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.service; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.text.format.DateUtils; -import android.util.Log; - -import androidx.core.content.ContextCompat; - -import javax.inject.Inject; - -import cx.ring.application.JamiApplication; -import dagger.hilt.android.AndroidEntryPoint; - -import net.jami.services.PreferencesService; - -@AndroidEntryPoint -public class BootReceiver extends BroadcastReceiver { - private static final String TAG = BootReceiver.class.getSimpleName(); - - @Inject - PreferencesService mPreferencesService; - - public BootReceiver() {} - - @Override - public void onReceive(Context context, Intent intent) { - if (intent == null || intent.getAction() == null) - return; - final String action = intent.getAction(); - if (Intent.ACTION_BOOT_COMPLETED.equals(action) || - Intent.ACTION_REBOOT.equals(action) || - Intent.ACTION_MY_PACKAGE_REPLACED.equals(action)) - { - try { - //((JamiApplication) context.getApplicationContext()).getInjectionComponent().inject(this); - if (mPreferencesService.getSettings().isAllowOnStartup()) { - try { - ContextCompat.startForegroundService(context, new Intent(SyncService.ACTION_START) - .setClass(context, SyncService.class) - .putExtra(SyncService.EXTRA_TIMEOUT, 5 * DateUtils.SECOND_IN_MILLIS)); - } catch (IllegalStateException e) { - Log.e(TAG, "Error starting service", e); - } - } - } catch (Exception e) { - Log.e(TAG, "Can't start on boot", e); - } - } - } -} diff --git a/ring-android/app/src/main/java/cx/ring/service/BootReceiver.kt b/ring-android/app/src/main/java/cx/ring/service/BootReceiver.kt new file mode 100644 index 0000000000000000000000000000000000000000..1c09f5dfbdb10dc1a14647c60b2ab99d886da4a5 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/service/BootReceiver.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.service + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.text.format.DateUtils +import android.util.Log +import androidx.core.content.ContextCompat +import dagger.hilt.android.AndroidEntryPoint +import net.jami.services.PreferencesService +import javax.inject.Inject + +@AndroidEntryPoint +class BootReceiver : BroadcastReceiver() { + @Inject + lateinit var mPreferencesService: PreferencesService + + override fun onReceive(context: Context, intent: Intent) { + val action = intent.action ?: return + if (Intent.ACTION_BOOT_COMPLETED == action || Intent.ACTION_REBOOT == action || Intent.ACTION_MY_PACKAGE_REPLACED == action) { + try { + if (mPreferencesService.settings.isAllowOnStartup) { + try { + ContextCompat.startForegroundService(context, Intent(SyncService.ACTION_START) + .setClass(context, SyncService::class.java) + .putExtra(SyncService.EXTRA_TIMEOUT, 5 * DateUtils.SECOND_IN_MILLIS)) + } catch (e: IllegalStateException) { + Log.e(TAG, "Error starting service", e) + } + } + } catch (e: Exception) { + Log.e(TAG, "Can't start on boot", e) + } + } + } + + companion object { + private val TAG = BootReceiver::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/service/CallNotificationService.java b/ring-android/app/src/main/java/cx/ring/service/CallNotificationService.java deleted file mode 100644 index 522754d08928f9449a8a5fe09bd1ebf68387bdc3..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/service/CallNotificationService.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Rayan Osseiran <rayan.osseiran@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.service; - -import android.app.Notification; -import android.app.Service; -import android.content.Intent; -import android.content.pm.ServiceInfo; -import android.os.Build; -import android.os.IBinder; - -import androidx.annotation.Nullable; - -import javax.inject.Inject; - -import cx.ring.services.NotificationServiceImpl; -import dagger.hilt.android.AndroidEntryPoint; - -import net.jami.services.NotificationService; - -@AndroidEntryPoint -public class CallNotificationService extends Service { - public static final String ACTION_START = "START"; - public static final String ACTION_STOP = "STOP"; - - @Inject - NotificationService mNotificationService; - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - super.onStartCommand(intent, flags, startId); - if (ACTION_START.equals(intent.getAction())) { - Notification notification = (Notification) mNotificationService.showCallNotification(intent.getIntExtra(NotificationService.KEY_NOTIFICATION_ID, -1)); - if (notification != null) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) - startForeground(NotificationServiceImpl.NOTIF_CALL_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL | ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION); - else - startForeground(NotificationServiceImpl.NOTIF_CALL_ID, notification); - } - } else if (ACTION_STOP.equals(intent.getAction())) { - stopForeground(true); - stopSelf(); - mNotificationService.cancelCallNotification(); - } - return START_NOT_STICKY; - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } -} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/service/CallNotificationService.kt b/ring-android/app/src/main/java/cx/ring/service/CallNotificationService.kt new file mode 100644 index 0000000000000000000000000000000000000000..fe9c195144f8fae0fbf21000792224f17826d4ff --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/service/CallNotificationService.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Rayan Osseiran <rayan.osseiran@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.service + +import android.app.Notification +import android.app.Service +import android.content.Intent +import android.content.pm.ServiceInfo +import android.os.Build +import android.os.IBinder +import cx.ring.services.NotificationServiceImpl +import dagger.hilt.android.AndroidEntryPoint +import net.jami.services.NotificationService +import javax.inject.Inject + +@AndroidEntryPoint +class CallNotificationService : Service() { + @Inject + lateinit var mNotificationService: NotificationService + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + super.onStartCommand(intent, flags, startId) + if (ACTION_START == intent.action) { + val notification = mNotificationService.showCallNotification(intent.getIntExtra(NotificationService.KEY_NOTIFICATION_ID, -1)) as Notification? + if (notification != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + startForeground(NotificationServiceImpl.NOTIF_CALL_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL or ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION) + else + startForeground(NotificationServiceImpl.NOTIF_CALL_ID, notification) + } + } else if (ACTION_STOP == intent.action) { + stopForeground(true) + stopSelf() + mNotificationService.cancelCallNotification() + } + return START_NOT_STICKY + } + + override fun onBind(intent: Intent): IBinder? { + return null + } + + companion object { + const val ACTION_START = "START" + const val ACTION_STOP = "STOP" + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/service/OutgoingCallHandler.java b/ring-android/app/src/main/java/cx/ring/service/OutgoingCallHandler.java deleted file mode 100644 index 8194d969868d9439b8213098b0cda4042ce58724..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/service/OutgoingCallHandler.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.service; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -import cx.ring.R; -import cx.ring.client.CallActivity; -import net.jami.model.Uri; - -public class OutgoingCallHandler extends BroadcastReceiver { - public static final String KEY_CACHE_HAVE_RINGACCOUNT = "cache_haveRingAccount"; - public static final String KEY_CACHE_HAVE_SIPACCOUNT = "cache_haveSipAccount"; - private static final String TAG = OutgoingCallHandler.class.getSimpleName(); - - @Override - public void onReceive(Context context, Intent intent) { - if (intent == null || !Intent.ACTION_NEW_OUTGOING_CALL.equals(intent.getAction())) - return; - - String phoneNumber = getResultData(); - if (phoneNumber == null) - phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); - - SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context); - boolean systemDialer = sharedPreferences.getBoolean(context.getString(R.string.pref_systemDialer_key), false); - if (systemDialer) { - boolean systemDialerSip = sharedPreferences.getBoolean(KEY_CACHE_HAVE_SIPACCOUNT, false); - boolean systemDialerRing = sharedPreferences.getBoolean(KEY_CACHE_HAVE_RINGACCOUNT, false); - - Uri uri = Uri.fromString(phoneNumber); - boolean isRingId = uri.isHexId(); - if ((!isRingId && systemDialerSip) || (isRingId && systemDialerRing) || uri.isSingleIp()) { - Intent i = new Intent(Intent.ACTION_CALL) - .setClass(context, CallActivity.class) - .setData(android.net.Uri.parse(phoneNumber)) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - context.startActivity(i); - setResultData(null); - } - } - } -} diff --git a/ring-android/app/src/main/java/cx/ring/service/OutgoingCallHandler.kt b/ring-android/app/src/main/java/cx/ring/service/OutgoingCallHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..3a738d1b18e88f5f4ef99d4b79360d52e4da5ff8 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/service/OutgoingCallHandler.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.service + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.preference.PreferenceManager +import cx.ring.R +import cx.ring.client.CallActivity + +class OutgoingCallHandler : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (Intent.ACTION_NEW_OUTGOING_CALL != intent.action) return + var phoneNumber = resultData + if (phoneNumber == null) phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER) + val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context) + val systemDialer = sharedPreferences.getBoolean(context.getString(R.string.pref_systemDialer_key), false) + if (systemDialer) { + val systemDialerSip = sharedPreferences.getBoolean(KEY_CACHE_HAVE_SIPACCOUNT, false) + val systemDialerRing = sharedPreferences.getBoolean(KEY_CACHE_HAVE_RINGACCOUNT, false) + val uri = net.jami.model.Uri.fromString(phoneNumber!!) + val isRingId = uri.isHexId + if (!isRingId && systemDialerSip || isRingId && systemDialerRing || uri.isSingleIp) { + val i = Intent(Intent.ACTION_CALL) + .setClass(context, CallActivity::class.java) + .setData(Uri.parse(phoneNumber)) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + context.startActivity(i) + resultData = null + } + } + } + + companion object { + const val KEY_CACHE_HAVE_RINGACCOUNT = "cache_haveRingAccount" + const val KEY_CACHE_HAVE_SIPACCOUNT = "cache_haveSipAccount" + private val TAG = OutgoingCallHandler::class.java.simpleName + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.kt b/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.kt index d567f54b6d276ccabd595887db5547ef318aa50d..1596ad3d6f930713441b710dbfd8631b57ab238a 100644 --- a/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.kt +++ b/ring-android/app/src/main/java/cx/ring/services/ContactServiceImpl.kt @@ -38,6 +38,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers import net.jami.model.Contact import net.jami.model.Conversation import net.jami.model.Phone +import net.jami.model.Profile import net.jami.services.AccountService import net.jami.services.ContactService import net.jami.services.DeviceRuntimeService @@ -183,12 +184,7 @@ class ContactServiceImpl(val mContext: Context, preferenceService: PreferencesSe val indexPhoto = result.getColumnIndex(ContactsContract.Data.PHOTO_ID) val indexStared = result.getColumnIndex(ContactsContract.Contacts.STARRED) val contactId = result.getLong(indexId) - Log.d( - TAG, - "Contact name: " + result.getString(indexName) + " id:" + contactId + " key:" + result.getString( - indexKey - ) - ) + Log.d(TAG, "Contact name: " + result.getString(indexName) + " id:" + contactId + " key:" + result.getString(indexKey)) contact = Contact(net.jami.model.Uri.fromString(contentUri.toString())) contact.setSystemContactInfo( contactId, @@ -208,7 +204,7 @@ class ContactServiceImpl(val mContext: Context, preferenceService: PreferencesSe if (contact == null) { Log.d(TAG, "findContactByIdFromSystem: findById $id can't find contact.") } - return contact!! + return contact } private fun fillContactDetails(contact: Contact) { @@ -219,12 +215,9 @@ class ContactServiceImpl(val mContext: Context, preferenceService: PreferencesSe CONTACTS_PHONES_PROJECTION, ID_SELECTION, arrayOf(contact.id.toString()), null ) if (cursorPhones != null) { - val indexNumber = - cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) - val indexType = - cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE) - val indexLabel = - cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LABEL) + val indexNumber = cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) + val indexType = cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE) + val indexLabel = cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.LABEL) while (cursorPhones.moveToNext()) { contact.addNumber( cursorPhones.getString(indexNumber), @@ -232,19 +225,12 @@ class ContactServiceImpl(val mContext: Context, preferenceService: PreferencesSe cursorPhones.getString(indexLabel), Phone.NumberType.TEL ) - Log.d( - TAG, - "Phone:" + cursorPhones.getString( - cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER) - ) - ) + Log.d(TAG, "Phone:" + cursorPhones.getString(cursorPhones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))) } cursorPhones.close() } - val baseUri = - ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contact.id) - val targetUri = - Uri.withAppendedPath(baseUri, ContactsContract.Contacts.Data.CONTENT_DIRECTORY) + val baseUri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contact.id) + val targetUri = Uri.withAppendedPath(baseUri, ContactsContract.Contacts.Data.CONTENT_DIRECTORY) val cursorSip = contentResolver.query( targetUri, CONTACTS_SIP_PROJECTION, @@ -257,22 +243,15 @@ class ContactServiceImpl(val mContext: Context, preferenceService: PreferencesSe ) if (cursorSip != null) { val indexMime = cursorSip.getColumnIndex(ContactsContract.Data.MIMETYPE) - val indexSip = - cursorSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS) - val indexType = - cursorSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.TYPE) - val indexLabel = - cursorSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.LABEL) + val indexSip = cursorSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.SIP_ADDRESS) + val indexType = cursorSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.TYPE) + val indexLabel = cursorSip.getColumnIndex(ContactsContract.CommonDataKinds.SipAddress.LABEL) while (cursorSip.moveToNext()) { val contactMime = cursorSip.getString(indexMime) val contactNumber = cursorSip.getString(indexSip) - if (!contactMime.contentEquals(ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE) || net.jami.model.Uri.fromString( - contactNumber - ).isHexId || "ring".equals( - cursorSip.getString(indexLabel), - ignoreCase = true - ) - ) { + if (contactMime != ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE + || net.jami.model.Uri.fromString(contactNumber).isHexId + || "ring".equals(cursorSip.getString(indexLabel),ignoreCase = true)) { contact.addNumber( contactNumber, cursorSip.getInt(indexType), @@ -328,27 +307,20 @@ class ContactServiceImpl(val mContext: Context, preferenceService: PreferencesSe fillContactDetails(contact) } result.close() - if (contact == null || contact.phones == null || contact.phones.isEmpty()) { + if (contact?.phones == null || contact.phones.isEmpty()) { return null } } catch (e: Exception) { - Log.d( - TAG, - "findContactBySipNumberFromSystem: Error while searching for contact number=$number", - e - ) + Log.d(TAG, "findContactBySipNumberFromSystem: Error while searching for contact number=$number", e) } - return contact!! + return contact } public override fun findContactByNumberFromSystem(number: String): Contact? { var contact: Contact? = null val contentResolver = mContext.contentResolver try { - val uri = Uri.withAppendedPath( - ContactsContract.PhoneLookup.CONTENT_FILTER_URI, - Uri.encode(number) - ) + val uri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)) val result = contentResolver.query(uri, PHONELOOKUP_PROJECTION, null, null, null) if (result == null) { Log.d(TAG, "findContactByNumberFromSystem: $number can't find contact.") @@ -367,10 +339,7 @@ class ContactServiceImpl(val mContext: Context, preferenceService: PreferencesSe result.getLong(indexPhoto) ) fillContactDetails(contact) - Log.d( - TAG, - "findContactByNumberFromSystem: " + number + " found " + contact.displayName - ) + Log.d(TAG, "findContactByNumberFromSystem: " + number + " found " + contact.displayName) } result.close() } catch (e: Exception) { @@ -386,12 +355,12 @@ class ContactServiceImpl(val mContext: Context, preferenceService: PreferencesSe override fun loadContactData(contact: Contact, accountId: String): Completable { if (!contact.detailsLoaded) { - val profile: Single<Tuple<String?, Any?>> = + val profile: Single<Profile> = if (contact.isFromSystem) loadSystemContactData(contact) else loadVCardContactData(contact, accountId) return profile - .doOnSuccess { p: Tuple<String?, Any?> -> contact.setProfile(p.first, p.second) } - .doOnError { e: Throwable? -> contact.setProfile(null, null) } + .doOnSuccess { p -> contact.setProfile(p) } + .doOnError { e: Throwable -> contact.setProfile(null, null) } .ignoreElement() .onErrorComplete() } @@ -399,18 +368,13 @@ class ContactServiceImpl(val mContext: Context, preferenceService: PreferencesSe } override fun saveVCardContactData(contact: Contact, accountId: String, vcard: VCard) { - if (vcard != null) { - val profileData = VCardServiceImpl.readData(vcard) - contact.setProfile(profileData.first, profileData.second) - val filename = contact.primaryNumber + ".vcf" - VCardUtils.savePeerProfileToDisk( - vcard, accountId, filename, mContext.filesDir - ) - AvatarFactory.clearCache() - } + contact.setProfile(VCardServiceImpl.readData(vcard)) + val filename = contact.primaryNumber + ".vcf" + VCardUtils.savePeerProfileToDisk(vcard, accountId, filename, mContext.filesDir) + AvatarFactory.clearCache() } - override fun saveVCardContact(accountId: String, uri: String, displayName: String, picture: String): Single<VCard> { + override fun saveVCardContact(accountId: String, uri: String?, displayName: String?, picture: String?): Single<VCard> { return Single.fromCallable { val vcard = VCardUtils.writeData(uri, displayName, Base64.decode(picture, Base64.DEFAULT)) val filename = "$uri.vcf" @@ -419,23 +383,20 @@ class ContactServiceImpl(val mContext: Context, preferenceService: PreferencesSe } } - private fun loadVCardContactData(contact: Contact, accountId: String): Single<Tuple<String?, Any?>> { + private fun loadVCardContactData(contact: Contact, accountId: String): Single<Profile> { val id = contact.primaryNumber return Single.fromCallable<VCard> { VCardUtils.loadPeerProfileFromDisk(mContext.filesDir, "$id.vcf", accountId) } .map { vcard: VCard -> VCardServiceImpl.readData(vcard) } .subscribeOn(Schedulers.computation()) } - private fun loadSystemContactData(contact: Contact): Single<Tuple<String?, Any?>> { + private fun loadSystemContactData(contact: Contact): Single<Profile> { val contactName = contact.displayName val photoURI = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contact.id) return AndroidFileUtils - .loadBitmap( - mContext, - Uri.withAppendedPath(photoURI, ContactsContract.Contacts.Photo.DISPLAY_PHOTO) - ) - .map { bitmap: Bitmap? -> Tuple<String?, Any?>(contactName, bitmap) } - .onErrorReturn { e: Throwable? -> Tuple(contactName, null) } + .loadBitmap(mContext, Uri.withAppendedPath(photoURI, ContactsContract.Contacts.Photo.DISPLAY_PHOTO)) + .map { bitmap: Bitmap -> Profile(contactName, bitmap) } + .onErrorReturn { Profile(contactName, null) } .subscribeOn(Schedulers.io()) } diff --git a/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt b/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt index fe7abca958630155e4f6e7a3d0497daf1c6bf491..3e618299b50975c2b57a4d7f7ec75823948d779f 100644 --- a/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt +++ b/ring-android/app/src/main/java/cx/ring/services/HardwareServiceImpl.kt @@ -30,6 +30,7 @@ import android.media.AudioManager.OnAudioFocusChangeListener import android.media.MediaRecorder import android.media.projection.MediaProjection import android.os.Build +import android.util.Log import android.view.SurfaceHolder import android.view.TextureView import android.view.WindowManager @@ -50,7 +51,6 @@ import net.jami.model.Call.CallStatus import net.jami.model.Conference import net.jami.services.HardwareService import net.jami.services.PreferencesService -import net.jami.utils.Log import net.jami.utils.Tuple import java.io.File import java.lang.ref.WeakReference @@ -63,7 +63,7 @@ class HardwareServiceImpl( preferenceService: PreferencesService, uiScheduler: Scheduler ) : HardwareService(executor, preferenceService, uiScheduler), OnAudioFocusChangeListener, BluetoothChangeListener { - private val videoInputs: MutableMap<String?, Shm> = HashMap() + private val videoInputs: MutableMap<String, Shm> = HashMap() private val cameraService = CameraService(mContext) private val mRinger = Ringer(mContext) private val mAudioManager: AudioManager = mContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager @@ -78,12 +78,13 @@ class HardwareServiceImpl( private var mIsChoosePlugin = false private var mMediaHandlerId: String? = null private var mPluginCallId: String? = null + override fun initVideo(): Completable { Log.i(TAG, "initVideo()") return cameraService.init() } - override val maxResolutions: Observable<Tuple<Int, Int>> + override val maxResolutions: Observable<Tuple<Int?, Int?>> get() = cameraService.maxResolutions override val isVideoAvailable: Boolean get() = mContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) || cameraService.hasCamera() @@ -119,37 +120,29 @@ class HardwareServiceImpl( get() = mAudioManager.isSpeakerphoneOn private val RINGTONE_REQUEST = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT) - .setAudioAttributes( - AudioAttributesCompat.Builder() - .setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC) - .setUsage(AudioAttributesCompat.USAGE_NOTIFICATION_RINGTONE) - .setLegacyStreamType(AudioManager.STREAM_RING) - .build() - ) + .setAudioAttributes(AudioAttributesCompat.Builder() + .setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC) + .setUsage(AudioAttributesCompat.USAGE_NOTIFICATION_RINGTONE) + .setLegacyStreamType(AudioManager.STREAM_RING) + .build()) .setOnAudioFocusChangeListener(this) .build() private val CALL_REQUEST = AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT) - .setAudioAttributes( - AudioAttributesCompat.Builder() - .setContentType(AudioAttributesCompat.CONTENT_TYPE_SPEECH) - .setUsage(AudioAttributesCompat.USAGE_VOICE_COMMUNICATION) - .setLegacyStreamType(AudioManager.STREAM_VOICE_CALL) - .build() - ) + .setAudioAttributes(AudioAttributesCompat.Builder() + .setContentType(AudioAttributesCompat.CONTENT_TYPE_SPEECH) + .setUsage(AudioAttributesCompat.USAGE_VOICE_COMMUNICATION) + .setLegacyStreamType(AudioManager.STREAM_VOICE_CALL) + .build()) .setOnAudioFocusChangeListener(this) .build() private fun getFocus(request: AudioFocusRequestCompat?) { if (currentFocus === request) return - if (currentFocus != null) { - AudioManagerCompat.abandonAudioFocusRequest(mAudioManager, currentFocus!!) + currentFocus?.let { focus -> + AudioManagerCompat.abandonAudioFocusRequest(mAudioManager, focus) currentFocus = null } - if (request != null && AudioManagerCompat.requestAudioFocus( - mAudioManager, - request - ) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED - ) { + if (request != null && AudioManagerCompat.requestAudioFocus(mAudioManager, request) == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { currentFocus = request } } @@ -316,13 +309,11 @@ class HardwareServiceImpl( @Synchronized override fun onBluetoothStateChanged(status: Int) { Log.d(TAG, "bluetoothStateChanged to: $status") - val event = BluetoothEvent() + val event = BluetoothEvent(status == BluetoothHeadset.STATE_AUDIO_CONNECTED) if (status == BluetoothHeadset.STATE_AUDIO_CONNECTED) { Log.d(TAG, "BluetoothHeadset Connected") - event.connected = true } else if (status == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) { Log.d(TAG, "BluetoothHeadset Disconnected") - event.connected = false if (mShouldSpeakerphone) routeToSpeaker() } bluetoothEvents.onNext(event) @@ -330,31 +321,24 @@ class HardwareServiceImpl( override fun decodingStarted(id: String, shmPath: String, width: Int, height: Int, isMixer: Boolean) { Log.i(TAG, "decodingStarted() " + id + " " + width + "x" + height) - val shm = Shm() - shm.id = id - shm.w = width - shm.h = height + val shm = Shm(id, width, height) videoInputs[id] = shm - val weakSurfaceHolder = videoSurfaces[id] - if (weakSurfaceHolder != null) { - val holder = weakSurfaceHolder.get() - if (holder != null) { - shm.window = startVideo(id, holder.surface, width, height) - if (shm.window == 0L) { - Log.i(TAG, "DJamiService.decodingStarted() no window !") - val event = VideoEvent() - event.start = true - event.callId = shm.id - videoEvents.onNext(event) - return - } + videoSurfaces[id]?.get()?.let { holder -> + shm.window = startVideo(id, holder.surface, width, height) + if (shm.window == 0L) { + Log.i(TAG, "DJamiService.decodingStarted() no window !") val event = VideoEvent() + event.start = true event.callId = shm.id - event.started = true - event.w = shm.w - event.h = shm.h videoEvents.onNext(event) + return } + val event = VideoEvent() + event.callId = shm.id + event.started = true + event.w = shm.w + event.h = shm.h + videoEvents.onNext(event) } } @@ -529,7 +513,7 @@ class HardwareServiceImpl( cameraService.requestKeyFrame() } - override fun setBitrate(device: String?, bitrate: Int) { + override fun setBitrate(device: String, bitrate: Int) { cameraService.setBitrate(bitrate) } @@ -546,7 +530,7 @@ class HardwareServiceImpl( mIsCapturing = false } - override fun addVideoSurface(id: String?, holder: Any?) { + override fun addVideoSurface(id: String, holder: Any) { if (holder !is SurfaceHolder) { return } @@ -572,7 +556,7 @@ class HardwareServiceImpl( videoEvents.onNext(event) } - override fun updateVideoSurfaceId(currentId: String?, newId: String?) { + override fun updateVideoSurfaceId(currentId: String, newId: String) { Log.w(TAG, "updateVideoSurfaceId $currentId $newId") val surfaceHolder = videoSurfaces[currentId] ?: return val surface = surfaceHolder.get() @@ -589,7 +573,7 @@ class HardwareServiceImpl( surface?.let { addVideoSurface(newId, it) } } - override fun addPreviewVideoSurface(holder: Any?, conference: Conference?) { + override fun addPreviewVideoSurface(holder: Any, conference: Conference?) { if (holder !is TextureView) return Log.w(TAG, "addPreviewVideoSurface " + holder.hashCode() + " mCapturingId " + mCapturingId) @@ -601,7 +585,7 @@ class HardwareServiceImpl( } } - override fun updatePreviewVideoSurface(conference: Conference?) { + override fun updatePreviewVideoSurface(conference: Conference) { val old = mCameraPreviewCall.get() mCameraPreviewCall = WeakReference(conference) if (old !== conference && mIsCapturing) { @@ -611,7 +595,7 @@ class HardwareServiceImpl( } } - override fun removeVideoSurface(id: String?) { + override fun removeVideoSurface(id: String) { Log.i(TAG, "removeVideoSurface $id") videoSurfaces.remove(id) val shm = videoInputs[id] ?: return @@ -634,7 +618,7 @@ class HardwareServiceImpl( mCameraPreviewSurface.clear() } - override fun switchInput(id: String?, setDefaultCamera: Boolean) { + override fun switchInput(id: String, setDefaultCamera: Boolean) { Log.w(TAG, "switchInput $id") mCapturingId = cameraService.switchInput(setDefaultCamera) switchInput(id, "camera://$mCapturingId") @@ -670,10 +654,7 @@ class HardwareServiceImpl( override val videoDevices: List<String> get() = cameraService.cameraIds - private class Shm { - var id: String? = null - var w = 0 - var h = 0 + private class Shm (val id: String, val w: Int, val h: Int) { var window: Long = 0 } @@ -688,8 +669,8 @@ class HardwareServiceImpl( private val VIDEO_SIZE_FULL_HD = Point(1920, 1080) private val VIDEO_SIZE_ULTRA_HD = Point(3840, 2160) private val TAG = HardwareServiceImpl::class.simpleName!! - private var mCameraPreviewSurface = WeakReference<TextureView?>(null) - private var mCameraPreviewCall = WeakReference<Conference?>(null) - private val videoSurfaces = Collections.synchronizedMap(HashMap<String?, WeakReference<SurfaceHolder>>()) + private var mCameraPreviewSurface = WeakReference<TextureView>(null) + private var mCameraPreviewCall = WeakReference<Conference>(null) + private val videoSurfaces = Collections.synchronizedMap(HashMap<String, WeakReference<SurfaceHolder>>()) } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/services/LogServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/LogServiceImpl.kt similarity index 52% rename from ring-android/app/src/main/java/cx/ring/services/LogServiceImpl.java rename to ring-android/app/src/main/java/cx/ring/services/LogServiceImpl.kt index 169e6f490f9235eaf164a5ad923d3b46a6ab30b7..5eb1858ab9df48ff4d56070102f59573183e9c62 100644 --- a/ring-android/app/src/main/java/cx/ring/services/LogServiceImpl.java +++ b/ring-android/app/src/main/java/cx/ring/services/LogServiceImpl.kt @@ -17,43 +17,41 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -package cx.ring.services; +package cx.ring.services -import android.util.Log; +import android.util.Log +import net.jami.services.LogService -import net.jami.services.LogService; - -public class LogServiceImpl implements LogService { - - public void e(String tag, String message) { - Log.e(tag, message); +class LogServiceImpl : LogService { + override fun e(tag: String, message: String) { + Log.e(tag, message) } - public void d(String tag, String message) { - Log.d(tag, message); + override fun d(tag: String, message: String) { + Log.d(tag, message) } - public void w(String tag, String message) { - Log.w(tag, message); + override fun w(tag: String, message: String) { + Log.w(tag, message) } - public void i(String tag, String message) { - Log.i(tag, message); + override fun i(tag: String, message: String) { + Log.i(tag, message) } - public void e(String tag, String message, Throwable e) { - Log.e(tag, message, e); + override fun e(tag: String, message: String, e: Throwable) { + Log.e(tag, message, e) } - public void d(String tag, String message, Throwable e) { - Log.d(tag, message, e); + override fun d(tag: String, message: String, e: Throwable) { + Log.d(tag, message, e) } - public void w(String tag, String message, Throwable e) { - Log.w(tag, message, e); + override fun w(tag: String, message: String, e: Throwable) { + Log.w(tag, message, e) } - public void i(String tag, String message, Throwable e) { - Log.i(tag, message, e); + override fun i(tag: String, message: String, e: Throwable) { + Log.i(tag, message, e) } -} +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.kt b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.kt index 670963168b3ab88c8639a46835fe41b37f457179..f0123c874d30a157b490ddffa381952840b27d76 100644 --- a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.kt +++ b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.kt @@ -509,7 +509,7 @@ class NotificationServiceImpl( val myPic = account?.let { getContactPicture(it) } val userPerson = Person.Builder() .setKey(accountId) - .setName(if (profile == null || TextUtils.isEmpty(profile.first)) "You" else profile.first) + .setName(if (profile == null || TextUtils.isEmpty(profile.displayName)) "You" else profile.displayName) .setIcon(if (myPic == null) null else IconCompat.createWithBitmap(myPic)) .build() val history = NotificationCompat.MessagingStyle(userPerson) @@ -831,23 +831,22 @@ class NotificationServiceImpl( notificationManager.notify(notificationId, messageNotificationBuilder.build()) } - override fun getServiceNotification(): Any { - val intentHome = Intent(Intent.ACTION_VIEW) - .setClass(mContext, HomeActivity::class.java) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - val pendIntent = PendingIntent.getActivity(mContext, 0, intentHome, PendingIntent.FLAG_UPDATE_CURRENT) - val messageNotificationBuilder = NotificationCompat.Builder(mContext, NOTIF_CHANNEL_SERVICE) - messageNotificationBuilder - .setContentTitle(mContext.getText(R.string.app_name)) - .setContentText(mContext.getText(R.string.notif_background_service)) - .setSmallIcon(R.drawable.ic_ring_logo_white) - .setContentIntent(pendIntent) - .setVisibility(NotificationCompat.VISIBILITY_SECRET) - .setPriority(NotificationCompat.PRIORITY_MIN) - .setOngoing(true) - .setCategory(NotificationCompat.CATEGORY_SERVICE) - return messageNotificationBuilder.build() - } + override val serviceNotification: Any + get() { + val intentHome = Intent(Intent.ACTION_VIEW) + .setClass(mContext, HomeActivity::class.java) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + return NotificationCompat.Builder(mContext, NOTIF_CHANNEL_SERVICE) + .setContentTitle(mContext.getText(R.string.app_name)) + .setContentText(mContext.getText(R.string.notif_background_service)) + .setSmallIcon(R.drawable.ic_ring_logo_white) + .setContentIntent(PendingIntent.getActivity(mContext, 0, intentHome, PendingIntent.FLAG_UPDATE_CURRENT)) + .setVisibility(NotificationCompat.VISIBILITY_SECRET) + .setPriority(NotificationCompat.PRIORITY_MIN) + .setOngoing(true) + .setCategory(NotificationCompat.CATEGORY_SERVICE) + .build() + } override fun cancelTextNotification(accountId: String, contact: net.jami.model.Uri) { val notificationId = getTextNotificationId(accountId, contact) diff --git a/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.kt b/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.kt index 225893201c19005be6a248de8303c29b5d6beb4f..1e4e39e4160080d600ff851050e687fd06c2a311 100644 --- a/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.kt +++ b/ring-android/app/src/main/java/cx/ring/services/SharedPreferencesServiceImpl.kt @@ -21,64 +21,54 @@ package cx.ring.services import android.content.Context -import net.jami.services.PreferencesService -import java.util.HashMap import android.content.SharedPreferences -import java.util.HashSet -import cx.ring.utils.NetworkUtils -import cx.ring.application.JamiApplication +import android.os.Build import android.text.TextUtils -import cx.ring.utils.DeviceUtils -import cx.ring.R import androidx.appcompat.app.AppCompatDelegate -import android.os.Build import androidx.preference.PreferenceManager +import cx.ring.R +import cx.ring.application.JamiApplication +import cx.ring.utils.DeviceUtils +import cx.ring.utils.NetworkUtils import net.jami.model.Settings import net.jami.model.Uri import net.jami.services.AccountService import net.jami.services.DeviceRuntimeService +import net.jami.services.PreferencesService +import java.util.* -class SharedPreferencesServiceImpl(val mContext: Context, accountService: AccountService, deviceService: DeviceRuntimeService) : PreferencesService(accountService, deviceService) { +class SharedPreferencesServiceImpl(val mContext: Context, accountService: AccountService, deviceService: DeviceRuntimeService) + : PreferencesService(accountService, deviceService) { private val mNotifiedRequests: MutableMap<String, MutableSet<String>> = HashMap() override fun saveSettings(settings: Settings) { - val appPrefs = preferences - val edit = appPrefs.edit() - edit.clear() - edit.putBoolean(PREF_SYSTEM_CONTACTS, settings.isAllowSystemContacts) - edit.putBoolean(PREF_PLACE_CALLS, settings.isAllowPlaceSystemCalls) - edit.putBoolean(PREF_ON_STARTUP, settings.isAllowOnStartup) - edit.putBoolean(PREF_PUSH_NOTIFICATIONS, settings.isAllowPushNotifications) - edit.putBoolean(PREF_PERSISTENT_NOTIFICATION, settings.isAllowPersistentNotification) - edit.putBoolean(PREF_SHOW_TYPING, settings.isAllowTypingIndicator) - edit.putBoolean(PREF_SHOW_READ, settings.isAllowReadIndicator) - edit.putBoolean(PREF_BLOCK_RECORD, settings.isRecordingBlocked) - edit.putInt(PREF_NOTIFICATION_VISIBILITY, settings.notificationVisibility) - edit.apply() + preferences.edit() + .clear() + .putBoolean(PREF_SYSTEM_CONTACTS, settings.isAllowSystemContacts) + .putBoolean(PREF_PLACE_CALLS, settings.isAllowPlaceSystemCalls) + .putBoolean(PREF_ON_STARTUP, settings.isAllowOnStartup) + .putBoolean(PREF_PUSH_NOTIFICATIONS, settings.isAllowPushNotifications) + .putBoolean(PREF_PERSISTENT_NOTIFICATION, settings.isAllowPersistentNotification) + .putBoolean(PREF_SHOW_TYPING, settings.isAllowTypingIndicator) + .putBoolean(PREF_SHOW_READ, settings.isAllowReadIndicator) + .putBoolean(PREF_BLOCK_RECORD, settings.isRecordingBlocked) + .putInt(PREF_NOTIFICATION_VISIBILITY, settings.notificationVisibility) + .apply() } override fun loadSettings(): Settings { val appPrefs = preferences - val settings = userSettings ?: Settings() - settings.isAllowSystemContacts = - appPrefs.getBoolean(PREF_SYSTEM_CONTACTS, false) - settings.isAllowPlaceSystemCalls = - appPrefs.getBoolean(PREF_PLACE_CALLS, false) - settings.setAllowRingOnStartup(appPrefs.getBoolean(PREF_ON_STARTUP, true)) - settings.isAllowPushNotifications = - appPrefs.getBoolean(PREF_PUSH_NOTIFICATIONS, false) - settings.isAllowPersistentNotification = appPrefs.getBoolean( - PREF_PERSISTENT_NOTIFICATION, - false - ) - settings.isAllowTypingIndicator = - appPrefs.getBoolean(PREF_SHOW_TYPING, true) - settings.isAllowReadIndicator = - appPrefs.getBoolean(PREF_SHOW_READ, true) - settings.setBlockRecordIndicator(appPrefs.getBoolean(PREF_BLOCK_RECORD, false)) - settings.notificationVisibility = - appPrefs.getInt(PREF_NOTIFICATION_VISIBILITY, 0) - return settings + return userSettings ?: Settings().apply { + isAllowSystemContacts = appPrefs.getBoolean(PREF_SYSTEM_CONTACTS, false) + isAllowPlaceSystemCalls = appPrefs.getBoolean(PREF_PLACE_CALLS, false) + setAllowRingOnStartup(appPrefs.getBoolean(PREF_ON_STARTUP, true)) + isAllowPushNotifications = appPrefs.getBoolean(PREF_PUSH_NOTIFICATIONS, false) + isAllowPersistentNotification = appPrefs.getBoolean(PREF_PERSISTENT_NOTIFICATION, false) + isAllowTypingIndicator = appPrefs.getBoolean(PREF_SHOW_TYPING, true) + isAllowReadIndicator = appPrefs.getBoolean(PREF_SHOW_READ, true) + setBlockRecordIndicator(appPrefs.getBoolean(PREF_BLOCK_RECORD, false)) + notificationVisibility = appPrefs.getInt(PREF_NOTIFICATION_VISIBILITY, 0) + } } private fun saveRequests(accountId: String, requests: Set<String>) { @@ -114,40 +104,27 @@ class SharedPreferencesServiceImpl(val mContext: Context, accountService: Accoun return NetworkUtils.isConnectivityAllowed(mContext) } - override fun isPushAllowed(): Boolean { - val token = JamiApplication.instance?.pushToken - return settings.isAllowPushNotifications && !TextUtils.isEmpty(token) /*&& NetworkUtils.isPushAllowed(mContext, getSettings().isAllowMobileData())*/ - } + override val isPushAllowed: Boolean + get() { + val token = JamiApplication.instance?.pushToken + return settings.isAllowPushNotifications && !TextUtils.isEmpty(token) /*&& NetworkUtils.isPushAllowed(mContext, getSettings().isAllowMobileData())*/ + } - override fun getResolution(): Int { - return videoPreferences.getString( - PREF_RESOLUTION, - if (DeviceUtils.isTv(mContext)) mContext.getString(R.string.video_resolution_default_tv) - else mContext.getString(R.string.video_resolution_default) - )!!.toInt() - } + override val resolution: Int + get() = videoPreferences.getString(PREF_RESOLUTION, if (DeviceUtils.isTv(mContext)) mContext.getString(R.string.video_resolution_default_tv) else mContext.getString(R.string.video_resolution_default))!!.toInt() - override fun getBitrate(): Int { - return videoPreferences.getString( - PREF_BITRATE, - mContext.getString(R.string.video_bitrate_default) - )!!.toInt() - } + override val bitrate: Int + get() = videoPreferences.getString(PREF_BITRATE, mContext.getString(R.string.video_bitrate_default))!!.toInt() - override fun isHardwareAccelerationEnabled(): Boolean { - return videoPreferences.getBoolean(PREF_HW_ENCODING, true) - } - - override fun setDarkMode(enabled: Boolean) { - val edit = themePreferences.edit() - edit.putBoolean(PREF_DARK_MODE, enabled) - .apply() - applyDarkMode(enabled) - } + override val isHardwareAccelerationEnabled: Boolean + get() = videoPreferences.getBoolean(PREF_HW_ENCODING, true) - override fun getDarkMode(): Boolean { - return themePreferences.getBoolean(PREF_DARK_MODE, false) - } + override var darkMode: Boolean + get() = themePreferences.getBoolean(PREF_DARK_MODE, false) + set(enabled) { + themePreferences.edit().putBoolean(PREF_DARK_MODE, enabled).apply() + applyDarkMode(enabled) + } override fun loadDarkMode() { applyDarkMode(darkMode) @@ -190,15 +167,8 @@ class SharedPreferencesServiceImpl(val mContext: Context, accountService: Accoun private const val PREF_ACCEPT_IN_MAX_SIZE = "acceptIncomingFilesMaxSize" const val PREF_PLUGINS = "plugins" - @JvmStatic fun getConversationPreferences( - context: Context, - accountId: String, - conversationUri: Uri - ): SharedPreferences { - return context.getSharedPreferences( - accountId + "_" + conversationUri.uri, - Context.MODE_PRIVATE - ) + fun getConversationPreferences(context: Context, accountId: String, conversationUri: Uri): SharedPreferences { + return context.getSharedPreferences(accountId + "_" + conversationUri.uri, Context.MODE_PRIVATE) } } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/services/VCardServiceImpl.kt b/ring-android/app/src/main/java/cx/ring/services/VCardServiceImpl.kt index 9ee35c4ffc987bea533951453196ea906ef17495..353a0a980f5a40410a613d875f36263c8542d90c 100644 --- a/ring-android/app/src/main/java/cx/ring/services/VCardServiceImpl.kt +++ b/ring-android/app/src/main/java/cx/ring/services/VCardServiceImpl.kt @@ -35,11 +35,11 @@ import io.reactivex.rxjava3.core.Observable import io.reactivex.rxjava3.core.Single import io.reactivex.rxjava3.schedulers.Schedulers import net.jami.model.Account -import net.jami.utils.Tuple +import net.jami.model.Profile import java.io.File class VCardServiceImpl(private val mContext: Context) : VCardService() { - override fun loadProfile(account: Account): Observable<Tuple<String?, Any?>> { + override fun loadProfile(account: Account): Observable<Profile> { return loadProfile(mContext, account) } @@ -61,26 +61,31 @@ class VCardServiceImpl(private val mContext: Context) : VCardService() { } } - override fun saveVCardProfile(accountId: String, uri: String, displayName: String, picture: String): Single<VCard> { + override fun saveVCardProfile(accountId: String, uri: String?, displayName: String?, picture: String?): Single<VCard> { return Single.fromCallable { VCardUtils.writeData(uri, displayName, Base64.decode(picture, Base64.DEFAULT)) } .flatMap { vcard: VCard -> VCardUtils.saveLocalProfileToDisk(vcard, accountId, mContext.filesDir) } } - override fun loadVCardProfile(vcard: VCard): Single<Tuple<String?, Any?>> { + override fun loadVCardProfile(vcard: VCard): Single<Profile> { return Single.fromCallable { readData(vcard) } } - override fun peerProfileReceived(accountId: String, peerId: String, vcardFile: File): Single<Tuple<String?, Any?>> { - return VCardUtils.peerProfileReceived(mContext.filesDir, accountId, peerId, vcardFile) + override fun peerProfileReceived(accountId: String, peerId: String, vcard: File): Single<Profile> { + return VCardUtils.peerProfileReceived(mContext.filesDir, accountId, peerId, vcard) + .map { vc -> readData(vc) } + } + + override fun accountProfileReceived(accountId: String, vcardFile: File): Single<Profile> { + return VCardUtils.accountProfileReceived(mContext.filesDir, accountId, vcardFile) .map { vcard -> readData(vcard) } } - override fun base64ToBitmap(base64: String): Any? { + override fun base64ToBitmap(base64: String?): Any? { return BitmapUtils.base64ToBitmap(base64) } companion object { - fun loadProfile(context: Context, account: Account): Observable<Tuple<String?, Any?>> { + fun loadProfile(context: Context, account: Account): Observable<Profile> { synchronized(account) { var ret = account.loadedProfile if (ret == null) { @@ -94,12 +99,12 @@ class VCardServiceImpl(private val mContext: Context) : VCardService() { } } - fun readData(vcard: VCard?): Tuple<String?, Any?> { + fun readData(vcard: VCard?): Profile { return readData(VCardUtils.readData(vcard)) } - fun readData(profile: Tuple<String?, ByteArray?>): Tuple<String?, Any?> { - return Tuple(profile.first, BitmapUtils.bytesToBitmap(profile.second)) + private fun readData(profile: Pair<String?, ByteArray?>): Profile { + return Profile(profile.first, BitmapUtils.bytesToBitmap(profile.second)) } } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/settings/AccountFragment.java b/ring-android/app/src/main/java/cx/ring/settings/AccountFragment.java deleted file mode 100644 index 100c966ef180ba87ca499cefdddfdecb6547b5d4..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/settings/AccountFragment.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2004-2019 Savoir-faire Linux Inc. - * - * Author: AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.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 cx.ring.settings; - -import android.app.Activity; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.Fragment; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import javax.inject.Inject; - -import cx.ring.R; -import cx.ring.account.AccountEditionFragment; -import cx.ring.account.JamiAccountSummaryFragment; -import cx.ring.application.JamiApplication; -import cx.ring.client.HomeActivity; -import cx.ring.databinding.FragAccountBinding; -import dagger.hilt.android.AndroidEntryPoint; -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.disposables.CompositeDisposable; - -import net.jami.services.AccountService; - -@AndroidEntryPoint -public class AccountFragment extends Fragment implements ViewTreeObserver.OnScrollChangedListener { - public static final String TAG = AccountFragment.class.getSimpleName(); - private static final int SCROLL_DIRECTION_UP = -1; - - public static AccountFragment newInstance(@NonNull String accountId) { - Bundle bundle = new Bundle(); - bundle.putString(AccountEditionFragment.ACCOUNT_ID_KEY, accountId); - AccountFragment accountFragment = new AccountFragment(); - accountFragment.setArguments(bundle); - return accountFragment; - } - - private FragAccountBinding mBinding; - private final CompositeDisposable mDisposable = new CompositeDisposable(); - - @Inject - AccountService mAccountService; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - mBinding = FragAccountBinding.inflate(inflater, container, false); - return mBinding.getRoot(); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - mBinding = null; - mDisposable.clear(); - } - - @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - setHasOptionsMenu(true); - mBinding.scrollview.getViewTreeObserver().addOnScrollChangedListener(this); - mBinding.settingsChangePassword.setOnClickListener(v -> ((JamiAccountSummaryFragment) getParentFragment()).onPasswordChangeAsked()); - mBinding.settingsExport.setOnClickListener(v -> ((JamiAccountSummaryFragment) getParentFragment()).onClickExport()); - - String accountId = getArguments() != null ? getArguments().getString(AccountEditionFragment.ACCOUNT_ID_KEY) : null; - mDisposable.add(mAccountService.getAccountSingle(accountId) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(account -> { - mBinding.settingsChangePassword.setVisibility(account.hasManager() ? View.GONE : View.VISIBLE); - mBinding.settingsExport.setVisibility(account.hasManager() ? View.GONE : View.VISIBLE); - mBinding.systemChangePasswordTitle.setText(account.hasPassword()? R.string.account_password_change : R.string.account_password_set); - mBinding.settingsDeleteAccount.setOnClickListener(v -> { - AlertDialog deleteDialog = createDeleteDialog(account.getAccountID()); - deleteDialog.show(); - }); - mBinding.settingsBlackList.setOnClickListener(v -> { - JamiAccountSummaryFragment summaryFragment = ((JamiAccountSummaryFragment) getParentFragment()); - if (summaryFragment != null) { - summaryFragment.goToBlackList(account.getAccountID()); - } - }); - }, e -> { - JamiAccountSummaryFragment summaryFragment = ((JamiAccountSummaryFragment) getParentFragment()); - if (summaryFragment != null) { - summaryFragment.popBackStack(); - } - })); - } - - @Override - public void onScrollChanged() { - if (mBinding != null) { - Activity activity = getActivity(); - if (activity instanceof HomeActivity) - ((HomeActivity) activity).setToolbarElevation(mBinding.scrollview.canScrollVertically(SCROLL_DIRECTION_UP)); - } - } - - @NonNull - private AlertDialog createDeleteDialog(String accountId) { - AlertDialog alertDialog = new MaterialAlertDialogBuilder(requireContext()) - .setMessage(R.string.account_delete_dialog_message) - .setTitle(R.string.account_delete_dialog_title) - .setPositiveButton(R.string.menu_delete, (dialog, whichButton) -> mAccountService.removeAccount(accountId)) - .setNegativeButton(android.R.string.cancel, null) - .create(); - Activity activity = getActivity(); - if (activity != null) - alertDialog.setOwnerActivity(getActivity()); - return alertDialog; - } - -} diff --git a/ring-android/app/src/main/java/cx/ring/settings/AccountFragment.kt b/ring-android/app/src/main/java/cx/ring/settings/AccountFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..9c404e2c71427ba9608fec7ab7b1e915e96d9dbc --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/settings/AccountFragment.kt @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2004-2019 Savoir-faire Linux Inc. + * + * Author: AmirHossein Naghshzan <amirhossein.naghshzan@savoirfairelinux.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 cx.ring.settings + +import android.app.Activity +import android.content.DialogInterface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.ViewTreeObserver.OnScrollChangedListener +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.Fragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import cx.ring.R +import cx.ring.account.AccountEditionFragment +import cx.ring.account.JamiAccountSummaryFragment +import cx.ring.client.HomeActivity +import cx.ring.databinding.FragAccountBinding +import dagger.hilt.android.AndroidEntryPoint +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.disposables.CompositeDisposable +import net.jami.model.Account +import net.jami.services.AccountService +import javax.inject.Inject + +@AndroidEntryPoint +class AccountFragment : Fragment(), OnScrollChangedListener { + private var mBinding: FragAccountBinding? = null + private val mDisposable = CompositeDisposable() + + @Inject + lateinit var mAccountService: AccountService + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + return FragAccountBinding.inflate(inflater, container, false).apply { + scrollview.viewTreeObserver.addOnScrollChangedListener(this@AccountFragment) + settingsChangePassword.setOnClickListener { (parentFragment as JamiAccountSummaryFragment).onPasswordChangeAsked() } + settingsExport.setOnClickListener { (parentFragment as JamiAccountSummaryFragment).onClickExport() } + mBinding = this + }.root + } + + override fun onDestroyView() { + super.onDestroyView() + mBinding = null + mDisposable.clear() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + setHasOptionsMenu(true) + val accountId = requireArguments().getString(AccountEditionFragment.ACCOUNT_ID_KEY)!! + mDisposable.add(mAccountService.getAccountSingle(accountId) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ account: Account -> + mBinding?.let { binding -> + binding.settingsChangePassword.visibility = if (account.hasManager()) View.GONE else View.VISIBLE + binding.settingsExport.visibility = if (account.hasManager()) View.GONE else View.VISIBLE + binding.systemChangePasswordTitle.setText(if (account.hasPassword()) R.string.account_password_change else R.string.account_password_set) + binding.settingsDeleteAccount.setOnClickListener { createDeleteDialog(account.accountID).show() } + binding.settingsBlackList.setOnClickListener { + val summaryFragment = parentFragment as JamiAccountSummaryFragment? + summaryFragment?.goToBlackList(account.accountID) + } + } + }) { + val summaryFragment = parentFragment as JamiAccountSummaryFragment? + summaryFragment?.popBackStack() + }) + } + + override fun onScrollChanged() { + mBinding?.let { binding -> + val activity: Activity? = activity + if (activity is HomeActivity) + activity.setToolbarElevation(binding.scrollview.canScrollVertically(SCROLL_DIRECTION_UP)) + } + } + + private fun createDeleteDialog(accountId: String): AlertDialog { + val alertDialog = MaterialAlertDialogBuilder(requireContext()) + .setMessage(R.string.account_delete_dialog_message) + .setTitle(R.string.account_delete_dialog_title) + .setPositiveButton(R.string.menu_delete) { dialog: DialogInterface?, whichButton: Int -> + mAccountService.removeAccount(accountId) + } + .setNegativeButton(android.R.string.cancel, null) + .create() + activity?.let { activity -> alertDialog.setOwnerActivity(activity) } + return alertDialog + } + + companion object { + val TAG = AccountFragment::class.simpleName!! + private const val SCROLL_DIRECTION_UP = -1 + fun newInstance(accountId: String): AccountFragment { + val bundle = Bundle() + bundle.putString(AccountEditionFragment.ACCOUNT_ID_KEY, accountId) + val accountFragment = AccountFragment() + accountFragment.arguments = bundle + return accountFragment + } + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PathListAdapter.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PathListAdapter.java deleted file mode 100644 index 8f724edfb48cd8129d93d339a05ce183ac34cd86..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PathListAdapter.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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 cx.ring.settings.pluginssettings; - -import android.graphics.drawable.Drawable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import java.io.File; -import java.util.List; - -import cx.ring.R; -import cx.ring.utils.AndroidFileUtils; - -public class PathListAdapter extends RecyclerView.Adapter<PathListAdapter.PathViewHolder> { - private List<String> mList; - private PathListItemListener mListener; - public static final String TAG = PathListAdapter.class.getSimpleName(); - - PathListAdapter(List<String> pathList, PathListItemListener listener) { - mList = pathList; - mListener = listener; - } - - @NonNull - @Override - public PathViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.frag_path_list_item, parent, false); - return new PathViewHolder(view, mListener); - } - - @Override - public void onBindViewHolder(@NonNull PathViewHolder holder, int position) { - holder.setDetails(mList.get(position)); - } - - @Override - public int getItemCount() { - return mList.size(); - } - - public void updatePluginsList(List<String> listPaths) { - mList = listPaths; - notifyDataSetChanged(); - } - - class PathViewHolder extends RecyclerView.ViewHolder{ - private final ImageView pathIcon; - private final TextView pathTextView; - private String path; - - PathViewHolder(@NonNull View itemView, PathListItemListener listener) { - super(itemView); - // Views that should be updated by the update method - pathIcon = itemView.findViewById(R.id.path_item_icon); - pathTextView = itemView.findViewById(R.id.path_item_name); - - // Set listeners, we set the listeners on creation so details can be null - itemView.setOnClickListener(v -> listener.onPathItemClicked(path)); - } - - // update the viewHolder view - public void update(String s) { - // Set the plugin icon - File file = new File(s); - if (file.exists()) { - if (AndroidFileUtils.isImage(s)) { - pathTextView.setVisibility(View.GONE); - Drawable icon = Drawable.createFromPath(s); - if (icon != null) { - pathIcon.setImageDrawable(icon); - } - } else { - pathTextView.setVisibility(View.VISIBLE); - pathTextView.setText(AndroidFileUtils.getFileName(s)); - } - } - } - - public void setDetails(String path) { - this.path = path; - update(this.path); - } - } - - public interface PathListItemListener { - void onPathItemClicked(String path); - } -} diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PathListAdapter.kt b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PathListAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..e76e54e3ed274bf1c8eb61a52e02d1e0349ace29 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PathListAdapter.kt @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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 cx.ring.settings.pluginssettings + +import android.graphics.drawable.Drawable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import cx.ring.R +import cx.ring.utils.AndroidFileUtils +import java.io.File + +class PathListAdapter internal constructor( + private var mList: List<String>, + private val mListener: PathListItemListener +) : RecyclerView.Adapter<PathListAdapter.PathViewHolder>() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PathViewHolder { + val view = LayoutInflater.from(parent.context) + .inflate(R.layout.frag_path_list_item, parent, false) + return PathViewHolder(view, mListener) + } + + override fun onBindViewHolder(holder: PathViewHolder, position: Int) { + holder.setDetails(mList[position]) + } + + override fun getItemCount(): Int { + return mList.size + } + + fun updatePluginsList(listPaths: List<String>) { + mList = listPaths + notifyDataSetChanged() + } + + inner class PathViewHolder(itemView: View, listener: PathListItemListener) : RecyclerView.ViewHolder(itemView) { + private val pathIcon: ImageView = itemView.findViewById(R.id.path_item_icon) + private val pathTextView: TextView = itemView.findViewById(R.id.path_item_name) + private var path: String? = null + + // update the viewHolder view + fun update(s: String) { + // Set the plugin icon + val file = File(s) + if (file.exists()) { + if (AndroidFileUtils.isImage(s)) { + pathTextView.visibility = View.GONE + Drawable.createFromPath(s)?.let { icon -> pathIcon.setImageDrawable(icon) } + } else { + pathTextView.visibility = View.VISIBLE + pathTextView.text = file.name + } + } + } + + fun setDetails(path: String) { + this.path = path + update(path) + } + + init { + itemView.setOnClickListener { path?.let { path -> listener.onPathItemClicked(path) }} + } + } + + interface PathListItemListener { + fun onPathItemClicked(path: String) + } + + companion object { + val TAG = PathListAdapter::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginDetails.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginDetails.java deleted file mode 100644 index 377a70a399cdf56d13b634e6c246b32fd10ebf89..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginDetails.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.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 cx.ring.settings.pluginssettings; - -import android.graphics.drawable.Drawable; - -import net.jami.daemon.JamiService; - -import java.io.File; -import java.util.List; -import java.util.Map; - -/** - * Class that contains PluginDetails like name, rootPath - */ -public class PluginDetails { - public static final String TAG = PluginDetails.class.getSimpleName(); - private String name; - private String rootPath; - private Map<String, String> details; - private Drawable icon; - private boolean enabled; - private String mHandlerId; - - public PluginDetails(String name, String rootPath, boolean enabled) { - this.name = name; - this.rootPath = rootPath; - this.enabled = enabled; - details = getPluginDetails(); - setIcon(); - } - - public PluginDetails(String name, String rootPath, boolean enabled, String handlerId) { - this.name = name; - this.mHandlerId = handlerId; - this.rootPath = rootPath; - this.enabled = enabled; - details = getPluginDetails(); - setIcon(); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getRootPath() { return rootPath; } - - public String getmHandlerId() { return mHandlerId; } - - /** - * Returns the plugin activation status by the user - * @return boolean - */ - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - - public void setIcon() { - String iconPath = details.get("iconPath"); - - if (iconPath.endsWith("svg")) - iconPath = iconPath.replace(".svg", ".png"); - if (iconPath != null) { - File file = new File(iconPath); - if(file.exists()) { - icon = Drawable.createFromPath(iconPath); - } - } - } - - public Drawable getIcon() { - return icon; - } - - public Map<String, String> getPluginDetails() { - return JamiService.getPluginDetails(getRootPath()).toNative(); - } - - public List<Map<String,String>> getPluginPreferences() { - return JamiService.getPluginPreferences(getRootPath()).toNative(); - } - - public Map<String, String> getPluginPreferencesValues() { - return JamiService.getPluginPreferencesValues(getRootPath()); - } - - public boolean setPluginPreference(String key, String value) { - return JamiService.setPluginPreference(getRootPath(), key, value); - } - -} diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginDetails.kt b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginDetails.kt new file mode 100644 index 0000000000000000000000000000000000000000..a5a488ab572017fa9ffba8788ca3664604535c6b --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginDetails.kt @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.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 cx.ring.settings.pluginssettings + +import android.graphics.drawable.Drawable +import net.jami.daemon.JamiService +import java.io.File + +/** + * Class that contains PluginDetails like name, rootPath + */ +class PluginDetails(var name: String, rootPath: String, enabled: Boolean, val handlerId: String? = null) { + var rootPath: String = rootPath + private set + private val details: Map<String, String> = pluginDetails + var icon: Drawable? = null + private set + + /** + * Returns the plugin activation status by the user + * @return boolean + */ + var isEnabled: Boolean = enabled + + fun setIcon() { + var iconPath = details["iconPath"] + if (iconPath != null) { + if (iconPath.endsWith("svg")) + iconPath = iconPath.replace(".svg", ".png") + val file = File(iconPath) + if (file.exists()) { + icon = Drawable.createFromPath(iconPath) + } + } + } + + private val pluginDetails: Map<String, String> + get() = JamiService.getPluginDetails(rootPath).toNative() + val pluginPreferences: List<Map<String, String>> + get() = JamiService.getPluginPreferences(rootPath).toNative() + val pluginPreferencesValues: Map<String, String> + get() = JamiService.getPluginPreferencesValues(rootPath) + + fun setPluginPreference(key: String, value: String): Boolean { + return JamiService.setPluginPreference(rootPath, key, value) + } + + companion object { + val TAG = PluginDetails::class.simpleName!! + } + + init { + setIcon() + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPathPreferenceFragment.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPathPreferenceFragment.java deleted file mode 100644 index 56c1d2574b172fee43c6c00a7f01318ce7b932b1..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPathPreferenceFragment.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.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 cx.ring.settings.pluginssettings; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import cx.ring.R; -import cx.ring.client.HomeActivity; -import cx.ring.databinding.FragPluginsPathPreferenceBinding; -import cx.ring.utils.AndroidFileUtils; -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; - -public class PluginPathPreferenceFragment extends Fragment implements PathListAdapter.PathListItemListener { - - public static final String TAG = PluginPathPreferenceFragment.class.getSimpleName(); - private static final int PATH_REQUEST_CODE = 1; - private final List<String> pathList = new ArrayList<>(); - private PluginDetails mPluginDetails; - private String mCurrentKey; - private String mCurrentValue; - private Context mContext; - private String subtitle; - private String[] supportedMimeTypes = {"*/*"}; - - private FragPluginsPathPreferenceBinding binding; - - public static PluginPathPreferenceFragment newInstance(PluginDetails pluginDetails, String preferenceKey) { - Bundle args = new Bundle(); - args.putString("name", pluginDetails.getName()); - args.putString("rootPath", pluginDetails.getRootPath()); - args.putBoolean("enabled", pluginDetails.isEnabled()); - args.putString("preferenceKey", preferenceKey); - PluginPathPreferenceFragment ppf = new PluginPathPreferenceFragment(); - ppf.setArguments(args); - return ppf; - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mContext = requireActivity(); - - Bundle arguments = getArguments(); - - if (arguments != null && arguments.getString("name") != null - && arguments.getString("rootPath") != null) { - mPluginDetails = new PluginDetails(arguments.getString("name"), arguments.getString("rootPath"), arguments.getBoolean("enabled")); - - List<Map<String, String>> mPreferencesAttributes = mPluginDetails.getPluginPreferences(); - if (mPreferencesAttributes != null && !mPreferencesAttributes.isEmpty()) { - mCurrentKey = arguments.getString("preferenceKey"); - mCurrentValue = mPluginDetails.getPluginPreferencesValues().get(mCurrentKey); - setHasOptionsMenu(true); - for (Map<String, String> preferenceAttributes : mPreferencesAttributes) { - if (preferenceAttributes.get("key").equals(mCurrentKey)) { - String mimeType = preferenceAttributes.get("mimeType"); - if (!TextUtils.isEmpty(mimeType)) - supportedMimeTypes = mimeType.split(","); - subtitle = mPluginDetails.getName() + " - " + preferenceAttributes.get("title"); - - String defaultPath = preferenceAttributes.get("defaultValue"); - if (!TextUtils.isEmpty(defaultPath)) { - defaultPath = defaultPath.substring(0, defaultPath.lastIndexOf("/")); - for (File file : new File(defaultPath).listFiles()) { - pathList.add(file.toString()); - } - break; - } - } - } - } - } - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - binding = FragPluginsPathPreferenceBinding.inflate(inflater, container, false); - - if (!pathList.isEmpty()) { - binding.pathPreferences.setAdapter(new PathListAdapter(pathList, this)); - } - - ((HomeActivity) requireActivity()).setToolbarTitle(R.string.menu_item_plugin_list); - - return binding.getRoot(); - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - - binding.pluginSettingSubtitle.setText(subtitle); - binding.pluginsPathPreferenceFab.setOnClickListener(v -> { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType(supportedMimeTypes[0]); - intent.putExtra(Intent.EXTRA_MIME_TYPES, supportedMimeTypes); - startActivityForResult(intent, PATH_REQUEST_CODE); - }); - - if (!mCurrentValue.isEmpty()) { - binding.currentPathItemIcon.setVisibility(View.VISIBLE); - File file = new File(mCurrentValue); - if (file.exists()) { - if (AndroidFileUtils.isImage(mCurrentValue)) { - binding.currentPathItemName.setVisibility(View.INVISIBLE); - Drawable icon = Drawable.createFromPath(mCurrentValue); - if (icon != null) { - binding.currentPathItemIcon.setImageDrawable(icon); - } - } else { - binding.currentPathItemName.setVisibility(View.VISIBLE); - binding.currentPathItemName.setText(AndroidFileUtils.getFileName(mCurrentValue)); - } - } - } else { - binding.currentPathItemIcon.setVisibility(View.INVISIBLE); - binding.currentPathItemName.setVisibility(View.INVISIBLE); - binding.pluginsPathPreferenceFab.performClick(); - } - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (requestCode == PATH_REQUEST_CODE && resultCode == Activity.RESULT_OK) { - if (data != null) { - Uri uri = data.getData(); - if (uri != null) { - AndroidFileUtils.getCacheFile(requireContext(), uri) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(file -> setPreferencePath(file.getAbsolutePath()), - e -> Toast.makeText(mContext, e.getMessage(), Toast.LENGTH_LONG).show()); - } - } - } - } - - @Override - public void onResume() { - ((HomeActivity) requireActivity()).setToolbarTitle(R.string.menu_item_plugin_list); - super.onResume(); - } - - @Override - public void onDestroyView() { - binding = null; - super.onDestroyView(); - } - - private void setPreferencePath(String path) { - if (mPluginDetails.setPluginPreference(mCurrentKey, path)) { - mCurrentValue = path; - if (!path.isEmpty()) { - binding.currentPathItemIcon.setVisibility(View.VISIBLE); - if (AndroidFileUtils.isImage(mCurrentValue)) { - Drawable icon = Drawable.createFromPath(mCurrentValue); - binding.currentPathItemIcon.setImageDrawable(icon); - binding.currentPathItemName.setVisibility(View.INVISIBLE); - } else { - binding.currentPathItemName.setText(AndroidFileUtils.getFileName(mCurrentValue)); - binding.currentPathItemName.setVisibility(View.VISIBLE); - } - } else { - binding.currentPathItemIcon.setVisibility(View.INVISIBLE); - binding.currentPathItemName.setVisibility(View.INVISIBLE); - } - } - } - - @Override - public void onPathItemClicked(String path) { - setPreferencePath(path); - } -} diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPathPreferenceFragment.kt b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPathPreferenceFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..4c3649ccc0c672b6659c844d4a65567b6a0c3973 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPathPreferenceFragment.kt @@ -0,0 +1,179 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Aline Gondim Santos <aline.gondimsantos@savoirfairelinux.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 cx.ring.settings.pluginssettings + +import android.app.Activity +import android.content.Intent +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import cx.ring.R +import cx.ring.client.HomeActivity +import cx.ring.databinding.FragPluginsPathPreferenceBinding +import cx.ring.settings.pluginssettings.PathListAdapter.PathListItemListener +import cx.ring.utils.AndroidFileUtils +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import java.io.File + +class PluginPathPreferenceFragment : Fragment(), PathListItemListener { + private val pathList: MutableList<String> = ArrayList() + private lateinit var mPluginDetails: PluginDetails + private lateinit var mCurrentKey: String + private var mCurrentValue: String? = null + private var subtitle: String = "" + private var supportedMimeTypes = arrayOf("*/*") + private var binding: FragPluginsPathPreferenceBinding? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val arguments = requireArguments() + val details = PluginDetails(arguments.getString("name")!!, arguments.getString("rootPath")!!, arguments.getBoolean("enabled")) + mPluginDetails = details + val key = arguments.getString("preferenceKey")!! + mCurrentKey = key + val mPreferencesAttributes = details.pluginPreferences + if (mPreferencesAttributes.isNotEmpty()) { + mCurrentValue = details.pluginPreferencesValues[key] + setHasOptionsMenu(true) + for (preferenceAttributes in mPreferencesAttributes) { + if (preferenceAttributes["key"] == key) { + val mimeType = preferenceAttributes["mimeType"] + if (mimeType != null && mimeType.isNotEmpty()) + supportedMimeTypes = mimeType.split(',').toTypedArray() + subtitle = details.name + " - " + preferenceAttributes["title"] + var defaultPath = preferenceAttributes["defaultValue"] + if (defaultPath != null && defaultPath.isNotEmpty()) { + defaultPath = defaultPath.substring(0, defaultPath.lastIndexOf("/")) + for (file in File(defaultPath).listFiles()!!) + pathList.add(file.toString()) + break + } + } + } + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + (requireActivity() as HomeActivity).setToolbarTitle(R.string.menu_item_plugin_list) + return FragPluginsPathPreferenceBinding.inflate(inflater, container, false).apply { + if (pathList.isNotEmpty()) + pathPreferences.adapter = PathListAdapter(pathList, this@PluginPathPreferenceFragment) + binding = this + pluginSettingSubtitle.text = subtitle + pluginsPathPreferenceFab.setOnClickListener { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = supportedMimeTypes[0] + putExtra(Intent.EXTRA_MIME_TYPES, supportedMimeTypes) + } + startActivityForResult(intent, PATH_REQUEST_CODE) + } + }.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val currentValue = mCurrentValue + val binding = binding ?: return + if (currentValue != null && currentValue.isNotEmpty()) { + binding.currentPathItemIcon.visibility = View.VISIBLE + val file = File(currentValue) + if (file.exists()) { + if (AndroidFileUtils.isImage(currentValue)) { + binding.currentPathItemName.visibility = View.INVISIBLE + val icon = Drawable.createFromPath(currentValue) + if (icon != null) { + binding.currentPathItemIcon.setImageDrawable(icon) + } + } else { + binding.currentPathItemName.visibility = View.VISIBLE + binding.currentPathItemName.text = file.name + } + } + } else { + binding.currentPathItemIcon.visibility = View.INVISIBLE + binding.currentPathItemName.visibility = View.INVISIBLE + binding.pluginsPathPreferenceFab.performClick() + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == PATH_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + data?.data?.let { uri -> + AndroidFileUtils.getCacheFile(requireContext(), uri) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ file: File -> setPreferencePath(file.absolutePath) }) + { e: Throwable -> context?.let { c -> Toast.makeText(c, e.message, Toast.LENGTH_LONG).show() }} + } + } + } + + override fun onResume() { + (requireActivity() as HomeActivity).setToolbarTitle(R.string.menu_item_plugin_list) + super.onResume() + } + + override fun onDestroyView() { + binding = null + super.onDestroyView() + } + + private fun setPreferencePath(path: String) { + if (mPluginDetails.setPluginPreference(mCurrentKey, path)) { + mCurrentValue = path + val binding = binding ?: return + if (path.isNotEmpty()) { + binding.currentPathItemIcon.visibility = View.VISIBLE + if (AndroidFileUtils.isImage(path)) { + val icon = Drawable.createFromPath(path) + binding.currentPathItemIcon.setImageDrawable(icon) + binding.currentPathItemName.visibility = View.INVISIBLE + } else { + binding.currentPathItemName.text = File(path).name + binding.currentPathItemName.visibility = View.VISIBLE + } + } else { + binding.currentPathItemIcon.visibility = View.INVISIBLE + binding.currentPathItemName.visibility = View.INVISIBLE + } + } + } + + override fun onPathItemClicked(path: String) { + setPreferencePath(path) + } + + companion object { + val TAG = PluginPathPreferenceFragment::class.simpleName!! + private const val PATH_REQUEST_CODE = 1 + fun newInstance(pluginDetails: PluginDetails, preferenceKey: String?): PluginPathPreferenceFragment { + val ppf = PluginPathPreferenceFragment() + ppf.arguments = Bundle().apply { + putString("name", pluginDetails.name) + putString("rootPath", pluginDetails.rootPath) + putBoolean("enabled", pluginDetails.isEnabled) + putString("preferenceKey", preferenceKey) + } + return ppf + } + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPreferencesDataStore.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPreferencesDataStore.java deleted file mode 100644 index 67020ff32c01ded8acce740b56d152b72c3042d1..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPreferencesDataStore.java +++ /dev/null @@ -1,186 +0,0 @@ -package cx.ring.settings.pluginssettings; - -import androidx.annotation.Nullable; -import androidx.preference.PreferenceDataStore; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import cx.ring.plugins.PluginUtils; - -public class PluginPreferencesDataStore extends PreferenceDataStore { - - private PluginDetails mPluginDetails; - private Map<String, String> mPreferenceTypes = new HashMap<>(); - private Map<String, String> preferencesValues; - private ReadWriteLock lock = new ReentrantReadWriteLock(); - - public PluginPreferencesDataStore(PluginDetails pluginDetails) { - mPluginDetails = pluginDetails; - preferencesValues = mPluginDetails.getPluginPreferencesValues(); - } - - public void addTomPreferenceTypes(Map<String, String> preferenceModel) { - Lock writeLock = lock.writeLock(); - try { - writeLock.lock(); - mPreferenceTypes.put(preferenceModel.get("key"), preferenceModel.get("type")); - } finally { - writeLock.unlock(); - } - } - - @Override - public void putBoolean(String key, boolean value) { - boolean success = mPluginDetails.setPluginPreference(key, value ? "1" : "0"); - if(success) { - notifyPreferencesValuesChange(); - } - } - - @Override - public void putString(String key, @Nullable String value) { - boolean success = mPluginDetails.setPluginPreference(key, value); - if(success) { - notifyPreferencesValuesChange(); - } - } - - @Override - public void putStringSet(String key, @Nullable Set<String> values) { - if(values != null) { - boolean success = mPluginDetails.setPluginPreference(key, - PluginUtils.listStringToStringList(new ArrayList<>(values))); - if(success) { - notifyPreferencesValuesChange(); - } - } - } - - @Override - public void putInt(String key, int value) { - Integer boxedValue = value; - boolean success = mPluginDetails.setPluginPreference(key, boxedValue.toString()); - if(success) { - notifyPreferencesValuesChange(); - } - } - - @Override - public void putLong(String key, long value) { - Long boxedValue = value; - boolean success = mPluginDetails.setPluginPreference(key, boxedValue.toString()); - if(success) { - notifyPreferencesValuesChange(); - } - } - - @Override - public void putFloat(String key, float value) { - Float boxedValue = value; - boolean success = mPluginDetails.setPluginPreference(key, boxedValue.toString()); - if(success) { - notifyPreferencesValuesChange(); - } - } - - @Nullable - @Override - public String getString(String key, @Nullable String defValue) { - String returnValue = defValue; - String value = getPreferencesValues().get(key); - if (value != null) { - returnValue = value; - } - return returnValue; - } - - @Nullable - @Override - public Set<String> getStringSet(String key, @Nullable Set<String> defValues) { - Set<String> returnValue = defValues; - - String value = getPreferencesValues().get(key); - - if(value != null) { - returnValue = new HashSet<>(PluginUtils.stringListToListString(value)); - } - - return returnValue; - } - - @Override - public int getInt(String key, int defValue) { - int returnValue = defValue; - String value = getPreferencesValues().get(key); - if (value != null) { - returnValue = Integer.parseInt(value); - } - return returnValue; - } - - @Override - public long getLong(String key, long defValue) { - long returnValue = defValue; - String value = getPreferencesValues().get(key); - if (value != null) { - returnValue = Long.parseLong(value); - } - return returnValue; - } - - @Override - public float getFloat(String key, float defValue) { - float returnValue = defValue; - String value = getPreferencesValues().get(key); - if (value != null) { - returnValue = Float.parseFloat(value); - } - return returnValue; - } - - @Override - public boolean getBoolean(String key, boolean defValue) { - boolean returnValue = defValue; - String value = getPreferencesValues().get(key); - if (value != null) { - returnValue = value.equals("1"); - } - return returnValue; - } - - /** - * Updates the preferencesValues map - * Use locks since the PreferenceInteraction is asynchronous - */ - public void notifyPreferencesValuesChange() { - Lock writeLock = lock.writeLock(); - try { - writeLock.lock(); - preferencesValues = mPluginDetails.getPluginPreferencesValues(); - } finally { - writeLock.unlock(); - } - } - - /** - * Returns the preferencesValues - * Use locks since the PreferenceInteraction is asynchronous - * @return preferencesValues - */ - private Map<String, String> getPreferencesValues() { - Lock readLock = lock.readLock(); - try { - readLock.lock(); - return preferencesValues; - } finally { - readLock.unlock(); - } - } -} diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPreferencesDataStore.kt b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPreferencesDataStore.kt new file mode 100644 index 0000000000000000000000000000000000000000..77502d4a9e5800150720846c0f9750497ee0908a --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginPreferencesDataStore.kt @@ -0,0 +1,118 @@ +package cx.ring.settings.pluginssettings + +import androidx.preference.PreferenceDataStore +import java.util.concurrent.locks.ReadWriteLock +import java.util.concurrent.locks.ReentrantReadWriteLock + +class PluginPreferencesDataStore(private val mPluginDetails: PluginDetails) : PreferenceDataStore() { + private val mPreferenceTypes: MutableMap<String, String> = HashMap() + + private var preferencesValues: Map<String, String> = mPluginDetails.pluginPreferencesValues + private val lock: ReadWriteLock = ReentrantReadWriteLock() + + fun addToPreferenceTypes(preferenceModel: Map<String, String>) { + val writeLock = lock.writeLock() + try { + writeLock.lock() + mPreferenceTypes[preferenceModel["key"]!!] = preferenceModel["type"]!! + } finally { + writeLock.unlock() + } + } + + override fun putBoolean(key: String, value: Boolean) { + if (mPluginDetails.setPluginPreference(key, if (value) "1" else "0")) { + notifyPreferencesValuesChange() + } + } + + override fun putString(key: String, value: String?) { + if (mPluginDetails.setPluginPreference(key, value ?: "")) { + notifyPreferencesValuesChange() + } + } + + override fun putStringSet(key: String, values: Set<String>?) { + if (values != null) { + if (mPluginDetails.setPluginPreference(key, values.joinToString(","))) { + notifyPreferencesValuesChange() + } + } + } + + override fun putInt(key: String, value: Int) { + if (mPluginDetails.setPluginPreference(key, value.toString())) { + notifyPreferencesValuesChange() + } + } + + override fun putLong(key: String, value: Long) { + if (mPluginDetails.setPluginPreference(key, value.toString())) { + notifyPreferencesValuesChange() + } + } + + override fun putFloat(key: String, value: Float) { + if (mPluginDetails.setPluginPreference(key, value.toString())) { + notifyPreferencesValuesChange() + } + } + + override fun getString(key: String, defValue: String?): String? { + return getPreferencesValues()[key] ?: defValue + } + + override fun getStringSet(key: String, defValues: Set<String>?): Set<String>? { + return getPreferencesValues()[key]?.split(',')?.toSet() ?: defValues + } + + override fun getInt(key: String, defValue: Int): Int { + return getPreferencesValues()[key]?.toInt() ?: defValue + } + + override fun getLong(key: String, defValue: Long): Long { + return getPreferencesValues()[key]?.toLong() ?: defValue + } + + override fun getFloat(key: String, defValue: Float): Float { + return getPreferencesValues()[key]?.toFloat() ?: defValue + } + + override fun getBoolean(key: String, defValue: Boolean): Boolean { + var returnValue = defValue + val value = getPreferencesValues()[key] + if (value != null) { + returnValue = value == "1" + } + return returnValue + } + + /** + * Updates the preferencesValues map + * Use locks since the PreferenceInteraction is asynchronous + */ + private fun notifyPreferencesValuesChange() { + val writeLock = lock.writeLock() + preferencesValues = try { + writeLock.lock() + mPluginDetails.pluginPreferencesValues + } finally { + writeLock.unlock() + } + } + + /** + * Returns the preferencesValues + * Use locks since the PreferenceInteraction is asynchronous + * @return preferencesValues + */ + private fun getPreferencesValues(): Map<String, String> { + val readLock = lock.readLock() + return try { + readLock.lock() + preferencesValues + } finally { + readLock.unlock() + } + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginSettingsFragment.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginSettingsFragment.java deleted file mode 100644 index daed74a8e870b560f0d48c41f974c67e33a0e54f..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginSettingsFragment.java +++ /dev/null @@ -1,382 +0,0 @@ -package cx.ring.settings.pluginssettings; - -import android.content.Context; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.preference.CheckBoxPreference; -import androidx.preference.DialogPreference; -import androidx.preference.DropDownPreference; -import androidx.preference.EditTextPreference; -import androidx.preference.ListPreference; -import androidx.preference.MultiSelectListPreference; -import androidx.preference.Preference; -import androidx.preference.PreferenceFragmentCompat; -import androidx.preference.PreferenceManager; -import androidx.preference.PreferenceScreen; -import androidx.preference.SeekBarPreference; -import androidx.preference.SwitchPreference; -import androidx.preference.TwoStatePreference; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; - -import net.jami.daemon.JamiService; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import cx.ring.R; -import cx.ring.client.HomeActivity; -import cx.ring.plugins.PluginPreferences; -import cx.ring.plugins.PluginUtils; - -public class PluginSettingsFragment extends PreferenceFragmentCompat { - public static final String TAG = PluginSettingsFragment.class.getSimpleName(); - private Context mContext; - private List<Map<String, String>> mPreferencesAttributes; - private PluginDetails pluginDetails; - private PluginPreferencesDataStore ppds; - - public static PluginSettingsFragment newInstance(PluginDetails pluginDetails) { - Bundle args = new Bundle(); - args.putString("name", pluginDetails.getName()); - args.putString("rootPath", pluginDetails.getRootPath()); - args.putBoolean("enabled", pluginDetails.isEnabled()); - PluginSettingsFragment psf = new PluginSettingsFragment(); - psf.setArguments(args); - return psf; - } - - @Override - public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { - - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mContext = requireActivity(); - - Bundle arguments = getArguments(); - - if (arguments != null && arguments.getString("name") != null - && arguments.getString("rootPath") != null) { - pluginDetails = new PluginDetails(arguments.getString("name"), - arguments.getString("rootPath"), arguments.getBoolean("enabled")); - - mPreferencesAttributes = pluginDetails.getPluginPreferences(); - - PreferenceManager preferenceManager = getPreferenceManager(); - ppds = new PluginPreferencesDataStore(pluginDetails); - preferenceManager.setPreferenceDataStore(ppds); - setHasOptionsMenu(true); - } - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - View root = super.onCreateView(inflater, container, savedInstanceState); - - PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(mContext); - screen.addPreference(createHeadPreference()); - for (Preference preference : createPreferences(mPreferencesAttributes)) { - screen.addPreference(preference); - } - setPreferenceScreen(screen); - return root; - } - - @Override - public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { - inflater.inflate(R.menu.plugin_edition, menu); - MenuItem item = menu.findItem(R.id.menuitem_delete); - item.setVisible(false); - super.onCreateOptionsMenu(menu, inflater); - } - - /** - * Takes a list of preferences attributes map - * Creates a preference View for each map of attributes in the preference - * - * @param preferencesAttributes A list of preferences attributes - * @return list of preferences - */ - private List<Preference> createPreferences(List<Map<String, String>> preferencesAttributes) { - List<Preference> preferencesViews = new ArrayList<>(); - if (preferencesAttributes != null) { - for (Map<String, String> preferenceAttributes : preferencesAttributes) { - String type = preferenceAttributes.get("type"); - // Call for each type the appropriate function member - if (type != null) { - switch (type) { - case "CheckBox": - preferencesViews.add(createCheckBoxPreference(preferenceAttributes)); - break; - case "DropDown": - preferencesViews.add(createDropDownPreference(preferenceAttributes)); - break; - case "EditText": - preferencesViews.add(createEditTextPreference(preferenceAttributes)); - break; - case "List": - preferencesViews.add(createListPreference(preferenceAttributes)); - break; - case "Path": - preferencesViews.add(createPathPreference(preferenceAttributes)); - break; - case "MultiSelectList": - preferencesViews. - add(createMultiSelectListPreference(preferenceAttributes)); - break; - case "SeekBar": - preferencesViews.add(createSeekBarPreference(preferenceAttributes)); - break; - case "Switch": - preferencesViews.add(createSwitchPreference(preferenceAttributes)); - break; - default: - break; - } - } - } - } - - return preferencesViews; - } - - private Preference createHeadPreference() - { - PluginPreferences preference = new PluginPreferences(mContext, pluginDetails); - preference.setResetClickListener(v -> new MaterialAlertDialogBuilder(mContext) - .setTitle(preference.getTitle()) - .setMessage(R.string.plugin_reset_preferences_ask) - .setPositiveButton(android.R.string.ok, (dialog, id) -> { - JamiService.resetPluginPreferencesValues(pluginDetails.getRootPath()); - ((HomeActivity) requireActivity()).popFragmentImmediate(); - }) - .setNegativeButton(android.R.string.cancel, (dialog, whichButton) -> { - /* Terminate with no action */ - }) - .show()); - preference.setInstallClickListener(v -> new MaterialAlertDialogBuilder(mContext) - .setMessage(R.string.account_delete_dialog_message) - .setTitle(R.string.plugin_uninstall_title) - .setPositiveButton(android.R.string.ok, (dialog, whichButton) -> { - pluginDetails.setEnabled(false); - JamiService.uninstallPlugin(pluginDetails.getRootPath()); - ((HomeActivity) requireActivity()).popFragmentImmediate(); - }) - .setNegativeButton(android.R.string.cancel, null) - .show()); - return preference; - } - - private CheckBoxPreference createCheckBoxPreference(Map<String, String> preferenceModel) { - CheckBoxPreference preference = new CheckBoxPreference(mContext); - setPreferenceAttributes(preference, preferenceModel); - setTwoStatePreferenceAttributes(preference, preferenceModel); - ppds.addTomPreferenceTypes(preferenceModel); - return preference; - } - - private DropDownPreference createDropDownPreference(Map<String, String> preferenceModel) { - DropDownPreference preference = new DropDownPreference(mContext); - setPreferenceAttributes(preference, preferenceModel); - setListPreferenceAttributes(preference, preferenceModel); - ppds.addTomPreferenceTypes(preferenceModel); - return preference; - } - - private EditTextPreference createEditTextPreference(Map<String, String> preferenceModel) { - EditTextPreference preference = new EditTextPreference(mContext); - setPreferenceAttributes(preference, preferenceModel); - setDialogPreferenceAttributes(preference, preferenceModel); - setEditTextAttributes(preference, preferenceModel); - ppds.addTomPreferenceTypes(preferenceModel); - return preference; - } - - private ListPreference createListPreference(Map<String, String> preferenceModel) { - ListPreference preference = new ListPreference(mContext); - setPreferenceAttributes(preference, preferenceModel); - setListPreferenceAttributes(preference, preferenceModel); - ppds.addTomPreferenceTypes(preferenceModel); - return preference; - } - - private Preference createPathPreference(Map<String, String> preferenceModel){ - Preference preference = new Preference(mContext); - preference.setOnPreferenceClickListener(p -> { - ((HomeActivity) mContext).gotToPluginPathPreference(pluginDetails, preferenceModel.get("key")); - return false; - }); - setPreferenceAttributes(preference, preferenceModel); - ppds.addTomPreferenceTypes(preferenceModel); - return preference; - } - - private MultiSelectListPreference createMultiSelectListPreference( - Map<String, String> preferenceModel) { - MultiSelectListPreference preference = new MultiSelectListPreference(mContext); - setPreferenceAttributes(preference, preferenceModel); - setMultiSelectListPreferenceAttributes(preference, preferenceModel); - return preference; - } - - private SwitchPreference createSwitchPreference(Map<String, String> preferenceModel) { - SwitchPreference preference = new SwitchPreference(mContext); - setPreferenceAttributes(preference, preferenceModel); - setTwoStatePreferenceAttributes(preference, preferenceModel); - return preference; - } - - private SeekBarPreference createSeekBarPreference(Map<String, String> preferenceModel) { - SeekBarPreference preference = new SeekBarPreference(mContext); - setPreferenceAttributes(preference, preferenceModel); - setSeekBarPreferenceAttributes(preference, preferenceModel); - return preference; - } - - /** - * Sets attributes that are common in the Preference base class - * - * @param preference the preference - * @param preferenceModel the map of attributes - */ - private void setPreferenceAttributes(Preference preference, - Map<String, String> preferenceModel) { - String key = preferenceModel.get("key"); - preference.setKey(key); - preference.setTitle(PluginUtils.getOrElse(preferenceModel.get("title"), "")); - preference.setSummary(PluginUtils.getOrElse(preferenceModel.get("summary"), "")); - } - - //TODO : add drawable icon - private void setDialogPreferenceAttributes(DialogPreference preference, - Map<String, String> preferenceModel) { - String dialogTitle = PluginUtils.getOrElse(preferenceModel.get("dialogTitle"), ""); - String dialogMessage = PluginUtils.getOrElse(preferenceModel.get("dialogMessage"), ""); - String positiveButtonText = PluginUtils.getOrElse(preferenceModel.get("positiveButtonText"), ""); - String negativeButtonText = PluginUtils.getOrElse(preferenceModel.get("negativeButtonText"), ""); - - if (!dialogTitle.isEmpty()) { - preference.setDialogTitle(dialogTitle); - } - - if (!dialogMessage.isEmpty()) { - preference.setDialogTitle(dialogMessage); - } - - if (!positiveButtonText.isEmpty()) { - preference.setPositiveButtonText(positiveButtonText); - } - - if (!negativeButtonText.isEmpty()) { - preference.setNegativeButtonText(negativeButtonText); - } - } - - /** - * Sets attributes specific to EditTextPreference - * Here we set the default value - * - * @param preference EditTextPreference - * @param preferenceModel the map of attributes - */ - private void setEditTextAttributes(EditTextPreference preference, - Map<String, String> preferenceModel) { - preference.setDefaultValue(preferenceModel.get("defaultValue")); - } - - /** - * Sets specific attributes for Preference that have for a base class ListPreference - * Sets the entries, entryValues and defaultValue - * - * @param preference the list preference - * @param preferenceModel the map of attributes - */ - private void setListPreferenceAttributes(ListPreference preference, - Map<String, String> preferenceModel) { - setDialogPreferenceAttributes(preference, preferenceModel); - String entries = PluginUtils.getOrElse(preferenceModel.get("entries"), ""); - preference.setEntries(PluginUtils.stringListToListString(entries). - toArray(new CharSequence[ 0 ])); - String entryValues = PluginUtils.getOrElse(preferenceModel.get("entryValues"), ""); - preference.setEntryValues(PluginUtils.stringListToListString(entryValues). - toArray(new CharSequence[ 0 ])); - preference.setDefaultValue(preferenceModel.get("defaultValue")); - } - - /** - * Sets specific attributes for Preference that have for a base class MultiSelectListPreference - * Sets the entries, entryValues and defaultValues - * - * @param preference the multi select list preference - * @param preferenceModel the map of attributes - */ - private void setMultiSelectListPreferenceAttributes(MultiSelectListPreference preference, - Map<String, String> preferenceModel) { - setDialogPreferenceAttributes(preference, preferenceModel); - String entries = PluginUtils.getOrElse(preferenceModel.get("entries"), "[]"); - preference.setEntries(PluginUtils.stringListToListString(entries). - toArray(new CharSequence[ 0 ])); - String entryValues = PluginUtils.getOrElse(preferenceModel.get("entryValues"), "[]"); - preference.setEntryValues(PluginUtils.stringListToListString(entryValues). - toArray(new CharSequence[ 0 ])); - String defaultValues = PluginUtils.getOrElse(preferenceModel.get("defaultValue"), "[]"); - preference.setEntryValues(PluginUtils.stringListToListString(entryValues). - toArray(new CharSequence[ 0 ])); - Set<CharSequence> set = new HashSet<>(PluginUtils.stringListToListString(defaultValues)); - preference.setDefaultValue(set); - } - - /** - * Sets specific attributes for setSeekBarPreference - * - * @param preference the seek bar preference - * @param preferenceModel the map of attributes - */ - private void setSeekBarPreferenceAttributes(SeekBarPreference preference, - Map<String, String> preferenceModel) { - int min = 0, max = 1, increment = 1; - int defaultValue = 0; - try { - min = Integer.parseInt(PluginUtils.getOrElse(preferenceModel.get("min"), "0")); - max = Integer.parseInt(PluginUtils.getOrElse(preferenceModel.get("max"), "1")); - increment = Integer.parseInt(PluginUtils.getOrElse(preferenceModel.get("increment"), "1")); - defaultValue = Integer.parseInt(PluginUtils.getOrElse(preferenceModel.get("defaultValue"), "[0")); - } catch (NumberFormatException e) { - Log.e(TAG, e.toString()); - } - preference.setMin(min); - preference.setMax(max); - preference.setSeekBarIncrement(increment); - preference.setAdjustable(Boolean.parseBoolean(PluginUtils.getOrElse(preferenceModel.get("adjustable"), "true"))); - preference.setDefaultValue(defaultValue); - preference.setShowSeekBarValue(true); - preference.setUpdatesContinuously(true); - } - - /** - * Sets specific attributes for twoStatePreference like Switch and CheckBox - * - * @param preference the two state preference - * @param preferenceModel the map of attributes - */ - private void setTwoStatePreferenceAttributes(TwoStatePreference preference, - Map<String, String> preferenceModel) { - preference.setChecked(ppds.getString("always", "1").equals("1")); - } -} diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginSettingsFragment.kt b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginSettingsFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..8267c85dd3d7caff45f6cf84fa954527a2ca2b5a --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginSettingsFragment.kt @@ -0,0 +1,313 @@ +package cx.ring.settings.pluginssettings + +import android.content.DialogInterface +import android.os.Bundle +import android.util.Log +import android.view.* +import androidx.preference.* +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import cx.ring.R +import cx.ring.client.HomeActivity +import cx.ring.plugins.PluginPreferences +import cx.ring.plugins.PluginUtils.stringListToListString +import net.jami.daemon.JamiService + +class PluginSettingsFragment : PreferenceFragmentCompat() { + private var mPreferencesAttributes: List<Map<String, String>>? = null + private var pluginDetails: PluginDetails? = null + private var ppds: PluginPreferencesDataStore? = null + override fun onCreatePreferences(savedInstanceState: Bundle, rootKey: String) {} + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val arguments = requireArguments() + val details = PluginDetails(arguments.getString("name")!!, arguments.getString("rootPath")!!, arguments.getBoolean("enabled")) + mPreferencesAttributes = details.pluginPreferences + val preferenceManager = preferenceManager + ppds = PluginPreferencesDataStore(details) + pluginDetails = details + preferenceManager.preferenceDataStore = ppds + setHasOptionsMenu(true) + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val root = super.onCreateView(inflater, container, savedInstanceState) + val screen = preferenceManager.createPreferenceScreen(requireContext()) + screen.addPreference(createHeadPreference()) + for (preference in createPreferences(mPreferencesAttributes)) { + screen.addPreference(preference) + } + preferenceScreen = screen + return root!! + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.plugin_edition, menu) + val item = menu.findItem(R.id.menuitem_delete) + item.isVisible = false + super.onCreateOptionsMenu(menu, inflater) + } + + /** + * Takes a list of preferences attributes map + * Creates a preference View for each map of attributes in the preference + * + * @param preferencesAttributes A list of preferences attributes + * @return list of preferences + */ + private fun createPreferences(preferencesAttributes: List<Map<String, String>>?): List<Preference> { + val preferencesViews: MutableList<Preference> = ArrayList() + if (preferencesAttributes != null) { + for (preferenceAttributes in preferencesAttributes) { + val type = preferenceAttributes["type"] + // Call for each type the appropriate function member + if (type != null) { + when (type) { + "CheckBox" -> preferencesViews.add(createCheckBoxPreference(preferenceAttributes)) + "DropDown" -> preferencesViews.add(createDropDownPreference(preferenceAttributes)) + "EditText" -> preferencesViews.add(createEditTextPreference(preferenceAttributes)) + "List" -> preferencesViews.add(createListPreference(preferenceAttributes)) + "Path" -> preferencesViews.add(createPathPreference(preferenceAttributes)) + "MultiSelectList" -> preferencesViews.add(createMultiSelectListPreference(preferenceAttributes)) + "SeekBar" -> preferencesViews.add(createSeekBarPreference(preferenceAttributes)) + "Switch" -> preferencesViews.add(createSwitchPreference(preferenceAttributes)) + else -> { + } + } + } + } + } + return preferencesViews + } + + private fun createHeadPreference(): Preference { + val preference = PluginPreferences(requireContext(), pluginDetails) + preference.setResetClickListener { + MaterialAlertDialogBuilder(requireContext()) + .setTitle(preference.title) + .setMessage(R.string.plugin_reset_preferences_ask) + .setPositiveButton(android.R.string.ok) { dialog: DialogInterface?, id: Int -> + JamiService.resetPluginPreferencesValues(pluginDetails!!.rootPath) + (requireActivity() as HomeActivity).popFragmentImmediate() + } + .setNegativeButton(android.R.string.cancel) { dialog: DialogInterface?, whichButton: Int -> } + .show() + } + preference.setInstallClickListener { + MaterialAlertDialogBuilder(requireContext()) + .setMessage(R.string.account_delete_dialog_message) + .setTitle(R.string.plugin_uninstall_title) + .setPositiveButton(android.R.string.ok) { dialog: DialogInterface?, whichButton: Int -> + pluginDetails!!.isEnabled = false + JamiService.uninstallPlugin(pluginDetails!!.rootPath) + (requireActivity() as HomeActivity).popFragmentImmediate() + } + .setNegativeButton(android.R.string.cancel, null) + .show() + } + return preference + } + + private fun createCheckBoxPreference(preferenceModel: Map<String, String>): CheckBoxPreference { + val preference = CheckBoxPreference(requireContext()) + setPreferenceAttributes(preference, preferenceModel) + setTwoStatePreferenceAttributes(preference, preferenceModel) + ppds!!.addToPreferenceTypes(preferenceModel) + return preference + } + + private fun createDropDownPreference(preferenceModel: Map<String, String>): DropDownPreference { + val preference = DropDownPreference(requireContext()) + setPreferenceAttributes(preference, preferenceModel) + setListPreferenceAttributes(preference, preferenceModel) + ppds!!.addToPreferenceTypes(preferenceModel) + return preference + } + + private fun createEditTextPreference(preferenceModel: Map<String, String>): EditTextPreference { + val preference = EditTextPreference(requireContext()) + setPreferenceAttributes(preference, preferenceModel) + setDialogPreferenceAttributes(preference, preferenceModel) + setEditTextAttributes(preference, preferenceModel) + ppds!!.addToPreferenceTypes(preferenceModel) + return preference + } + + private fun createListPreference(preferenceModel: Map<String, String>): ListPreference { + val preference = ListPreference(requireContext()) + setPreferenceAttributes(preference, preferenceModel) + setListPreferenceAttributes(preference, preferenceModel) + ppds!!.addToPreferenceTypes(preferenceModel) + return preference + } + + private fun createPathPreference(preferenceModel: Map<String, String>): Preference { + val preference = Preference(requireContext()) + preference.onPreferenceClickListener = Preference.OnPreferenceClickListener { + (requireActivity() as HomeActivity).gotToPluginPathPreference(pluginDetails!!, preferenceModel["key"]!!) + false + } + setPreferenceAttributes(preference, preferenceModel) + ppds!!.addToPreferenceTypes(preferenceModel) + return preference + } + + private fun createMultiSelectListPreference(preferenceModel: Map<String, String>): MultiSelectListPreference { + val preference = MultiSelectListPreference(requireContext()) + setPreferenceAttributes(preference, preferenceModel) + setMultiSelectListPreferenceAttributes(preference, preferenceModel) + return preference + } + + private fun createSwitchPreference(preferenceModel: Map<String, String>): SwitchPreference { + val preference = SwitchPreference(requireContext()) + setPreferenceAttributes(preference, preferenceModel) + setTwoStatePreferenceAttributes(preference, preferenceModel) + return preference + } + + private fun createSeekBarPreference(preferenceModel: Map<String, String>): SeekBarPreference { + val preference = SeekBarPreference(requireContext()) + setPreferenceAttributes(preference, preferenceModel) + setSeekBarPreferenceAttributes(preference, preferenceModel) + return preference + } + + /** + * Sets attributes that are common in the Preference base class + * + * @param preference the preference + * @param preferenceModel the map of attributes + */ + private fun setPreferenceAttributes(preference: Preference, preferenceModel: Map<String, String>) { + val key = preferenceModel["key"] + preference.key = key + preference.title = preferenceModel["title"] ?: "" + preference.summary = preferenceModel["summary"] ?: "" + } + + //TODO : add drawable icon + private fun setDialogPreferenceAttributes(preference: DialogPreference, preferenceModel: Map<String, String>) { + val dialogTitle = preferenceModel["dialogTitle"] ?: "" + val dialogMessage = preferenceModel["dialogMessage"] ?: "" + val positiveButtonText = preferenceModel["positiveButtonText"] ?: "" + val negativeButtonText = preferenceModel["negativeButtonText"] ?: "" + if (dialogTitle.isNotEmpty()) { + preference.dialogTitle = dialogTitle + } + if (dialogMessage.isNotEmpty()) { + preference.dialogTitle = dialogMessage + } + if (positiveButtonText.isNotEmpty()) { + preference.positiveButtonText = positiveButtonText + } + if (negativeButtonText.isNotEmpty()) { + preference.negativeButtonText = negativeButtonText + } + } + + /** + * Sets attributes specific to EditTextPreference + * Here we set the default value + * + * @param preference EditTextPreference + * @param preferenceModel the map of attributes + */ + private fun setEditTextAttributes( + preference: EditTextPreference, + preferenceModel: Map<String, String> + ) { + preference.setDefaultValue(preferenceModel["defaultValue"]) + } + + /** + * Sets specific attributes for Preference that have for a base class ListPreference + * Sets the entries, entryValues and defaultValue + * + * @param preference the list preference + * @param preferenceModel the map of attributes + */ + private fun setListPreferenceAttributes(preference: ListPreference, preferenceModel: Map<String, String>) { + setDialogPreferenceAttributes(preference, preferenceModel) + val entries = preferenceModel["entries"] ?: "" + preference.entries = entries.split(',').toTypedArray<CharSequence>() + val entryValues = preferenceModel["entryValues"] ?: "" + preference.entryValues = stringListToListString(entryValues).toTypedArray<CharSequence>() + preference.setDefaultValue(preferenceModel["defaultValue"]) + } + + /** + * Sets specific attributes for Preference that have for a base class MultiSelectListPreference + * Sets the entries, entryValues and defaultValues + * + * @param preference the multi select list preference + * @param preferenceModel the map of attributes + */ + private fun setMultiSelectListPreferenceAttributes( + preference: MultiSelectListPreference, + preferenceModel: Map<String, String> + ) { + setDialogPreferenceAttributes(preference, preferenceModel) + val entries = preferenceModel["entries"] ?: "[]" + preference.entries = stringListToListString(entries).toTypedArray<CharSequence>() + val entryValues = preferenceModel["entryValues"] ?: "[]" + preference.entryValues = stringListToListString(entryValues).toTypedArray<CharSequence>() + val defaultValues = preferenceModel["defaultValue"] ?: "[]" + preference.entryValues = stringListToListString(entryValues).toTypedArray<CharSequence>() + val set: Set<CharSequence> = HashSet<CharSequence>(stringListToListString(defaultValues)) + preference.setDefaultValue(set) + } + + /** + * Sets specific attributes for setSeekBarPreference + * + * @param preference the seek bar preference + * @param preferenceModel the map of attributes + */ + private fun setSeekBarPreferenceAttributes( + preference: SeekBarPreference, + preferenceModel: Map<String, String> + ) { + var min = 0 + var max = 1 + var increment = 1 + var defaultValue = 0 + try { + min = (preferenceModel["min"] ?: "0").toInt() + max = (preferenceModel["max"] ?: "1").toInt() + increment = (preferenceModel["increment"] ?: "1").toInt() + defaultValue = (preferenceModel["defaultValue"] ?: "[0").toInt() + } catch (e: NumberFormatException) { + Log.e(TAG, e.toString()) + } + preference.min = min + preference.max = max + preference.seekBarIncrement = increment + preference.isAdjustable = (preferenceModel["adjustable"] ?: "true").toBoolean() + preference.setDefaultValue(defaultValue) + preference.showSeekBarValue = true + preference.updatesContinuously = true + } + + /** + * Sets specific attributes for twoStatePreference like Switch and CheckBox + * + * @param preference the two state preference + * @param preferenceModel the map of attributes + */ + private fun setTwoStatePreferenceAttributes(preference: TwoStatePreference, preferenceModel: Map<String, String>) { + preference.isChecked = ppds!!.getString("always", "1") == "1" + } + + companion object { + val TAG = PluginSettingsFragment::class.simpleName!! + fun newInstance(pluginDetails: PluginDetails): PluginSettingsFragment { + val psf = PluginSettingsFragment() + psf.arguments = Bundle().apply { + putString("name", pluginDetails.name) + putString("rootPath", pluginDetails.rootPath) + putBoolean("enabled", pluginDetails.isEnabled) + } + return psf + } + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListAdapter.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListAdapter.java deleted file mode 100644 index 6d41be74d48067613f07133bec8259fcb01972fe..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListAdapter.java +++ /dev/null @@ -1,99 +0,0 @@ -package cx.ring.settings.pluginssettings; - -import android.graphics.drawable.Drawable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import java.util.List; - -import cx.ring.R; - -public class PluginsListAdapter extends RecyclerView.Adapter<PluginsListAdapter.PluginViewHolder> { - private List<PluginDetails> mList; - private final PluginListItemListener listener; - public static final String TAG = PluginsListAdapter.class.getSimpleName(); - - public PluginsListAdapter(List<PluginDetails> pluginsList, PluginListItemListener listener) { - this.mList = pluginsList; - this.listener = listener; - } - - @NonNull - @Override - public PluginViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.frag_plugins_list_item, parent, false); - return new PluginViewHolder(view, listener); - } - - @Override - public void onBindViewHolder(@NonNull PluginViewHolder holder, int position) { - holder.setDetails(mList.get(position)); - } - - @Override - public int getItemCount() { - return mList.size(); - } - - public void updatePluginsList(List<PluginDetails> listPlugins) { - mList = listPlugins; - notifyDataSetChanged(); - } - - static class PluginViewHolder extends RecyclerView.ViewHolder{ - private final ImageView pluginIcon; - private final TextView pluginNameTextView; - private final CheckBox pluginItemEnableCheckbox; - private PluginDetails details = null; - - PluginViewHolder(@NonNull View itemView, PluginListItemListener listener) { - super(itemView); - // Views that should be updated by the update method - pluginIcon = itemView.findViewById(R.id.plugin_item_icon); - pluginNameTextView = itemView.findViewById(R.id.plugin_item_name); - pluginItemEnableCheckbox = itemView.findViewById(R.id.plugin_item_enable_checkbox); - - // Set listeners, we set the listeners on creation so details can be null - itemView.setOnClickListener(v -> { - if (details != null) { - listener.onPluginItemClicked(details); - } - }); - - pluginItemEnableCheckbox.setOnClickListener(v -> { - if (details != null) { - details.setEnabled(!details.isEnabled()); - listener.onPluginEnabled(details); - } - }); - } - - public void setDetails(PluginDetails details) { - this.details = details; - update(this.details); - } - - // update the viewHolder view - public void update(PluginDetails details) { - pluginNameTextView.setText(details.getName()); - pluginItemEnableCheckbox.setChecked(details.isEnabled()); - // Set the plugin icon - Drawable icon = details.getIcon(); - if (icon != null) { - pluginIcon.setImageDrawable(icon); - } - } - } - - public interface PluginListItemListener { - void onPluginItemClicked(PluginDetails pluginDetails); - void onPluginEnabled(PluginDetails pluginDetails); - } -} diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListAdapter.kt b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListAdapter.kt new file mode 100644 index 0000000000000000000000000000000000000000..07fa3c9ecc7bb11ca183774c596b7f89fe3b20c1 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListAdapter.kt @@ -0,0 +1,75 @@ +package cx.ring.settings.pluginssettings + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CheckBox +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import cx.ring.R +import cx.ring.settings.pluginssettings.PluginsListAdapter.PluginViewHolder + +class PluginsListAdapter(private var mList: List<PluginDetails>, private val listener: PluginListItemListener) : + RecyclerView.Adapter<PluginViewHolder>() { + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PluginViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.frag_plugins_list_item, parent, false) + return PluginViewHolder(view, listener) + } + + override fun onBindViewHolder(holder: PluginViewHolder, position: Int) { + holder.setDetails(mList[position]) + } + + override fun getItemCount(): Int { + return mList.size + } + + fun updatePluginsList(listPlugins: List<PluginDetails>) { + mList = listPlugins + notifyDataSetChanged() + } + + class PluginViewHolder(itemView: View, listener: PluginListItemListener) : RecyclerView.ViewHolder(itemView) { + private val pluginIcon: ImageView = itemView.findViewById(R.id.plugin_item_icon) + private val pluginNameTextView: TextView = itemView.findViewById(R.id.plugin_item_name) + private val pluginItemEnableCheckbox: CheckBox = itemView.findViewById(R.id.plugin_item_enable_checkbox) + private var details: PluginDetails? = null + fun setDetails(details: PluginDetails) { + this.details = details + update(details) + } + + // update the viewHolder view + fun update(details: PluginDetails) { + pluginNameTextView.text = details.name + pluginItemEnableCheckbox.isChecked = details.isEnabled + // Set the plugin icon + val icon = details.icon + if (icon != null) { + pluginIcon.setImageDrawable(icon) + } + } + + init { + itemView.setOnClickListener { + details?.let { details -> listener.onPluginItemClicked(details) } + } + pluginItemEnableCheckbox.setOnClickListener { + details?.let { details -> + details.isEnabled = !details.isEnabled + listener.onPluginEnabled(details) + } + } + } + } + + interface PluginListItemListener { + fun onPluginItemClicked(pluginDetails: PluginDetails) + fun onPluginEnabled(pluginDetails: PluginDetails) + } + + companion object { + val TAG = PluginsListAdapter::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListSettingsFragment.java b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListSettingsFragment.java deleted file mode 100644 index 0c503ca28695129898d935cf6201953b8c5aaaa6..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListSettingsFragment.java +++ /dev/null @@ -1,194 +0,0 @@ -package cx.ring.settings.pluginssettings; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Toast; - -import androidx.activity.OnBackPressedCallback; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - -import com.google.android.material.snackbar.Snackbar; - -import net.jami.daemon.JamiService; - -import java.io.File; -import java.io.IOException; -import java.util.List; - -import cx.ring.R; -import cx.ring.account.JamiAccountSummaryFragment; -import cx.ring.client.HomeActivity; -import cx.ring.databinding.FragPluginsListSettingsBinding; -import cx.ring.plugins.PluginUtils; -import cx.ring.utils.AndroidFileUtils; -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.disposables.CompositeDisposable; - -public class PluginsListSettingsFragment extends Fragment implements PluginsListAdapter.PluginListItemListener { - - public static final String TAG = PluginsListSettingsFragment.class.getSimpleName(); - - private final OnBackPressedCallback mOnBackPressedCallback = new OnBackPressedCallback(false) { - @Override - public void handleOnBackPressed() { - mOnBackPressedCallback.setEnabled(false); - JamiAccountSummaryFragment fragment = (JamiAccountSummaryFragment) getParentFragment(); - if (fragment != null) { - fragment.popBackStack(); - } - } - }; - - private static final int ARCHIVE_REQUEST_CODE = 42; - - private FragPluginsListSettingsBinding binding; - private PluginsListAdapter mAdapter; - private final CompositeDisposable mCompositeDisposable = new CompositeDisposable(); - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - binding = FragPluginsListSettingsBinding.inflate(inflater, container, false); - - // use this setting to improve performance if you know that changes - // in content do not change the layout size of the RecyclerView - binding.pluginsList.setHasFixedSize(true); - - // specify an adapter (see also next example) - mAdapter = new PluginsListAdapter(PluginUtils.getInstalledPlugins(binding.pluginsList.getContext()), this); - binding.pluginsList.setAdapter(mAdapter); - - //Fab - binding.pluginsListSettingsFab.setOnClickListener(view -> { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("*/*"); - startActivityForResult(intent, ARCHIVE_REQUEST_CODE); - }); - - return binding.getRoot(); - } - - @Override - public void onResume() { - mOnBackPressedCallback.setEnabled(true); - super.onResume(); - } - - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - requireActivity().getOnBackPressedDispatcher().addCallback(this, mOnBackPressedCallback); - } - - /** - * Implements PluginListItemListener.onPluginItemClicked which is called when we click on - * a plugin list item - * @param pluginDetails instance of a plugin details that is sent to PluginSettingsFragment - */ - @Override - public void onPluginItemClicked(PluginDetails pluginDetails) { - ((HomeActivity) requireActivity()).gotToPluginSettings(pluginDetails); - } - - /** - * Implements PluginListItemListener.onPluginEnabled which is called when the checkbox - * associated with the plugin list item is called - * @param pluginDetails instance of a plugin details that is sent to PluginSettingsFragment - */ - @Override - public void onPluginEnabled(PluginDetails pluginDetails) { - - if(pluginDetails.isEnabled()) { - pluginDetails.setEnabled(PluginUtils.loadPlugin(pluginDetails.getRootPath())); - } else { - PluginUtils.unloadPlugin(pluginDetails.getRootPath()); - } - String status = pluginDetails.isEnabled()?"ON":"OFF"; - Toast.makeText(requireContext(), pluginDetails.getName() + " " + status, - Toast.LENGTH_SHORT).show(); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { - if (requestCode == ARCHIVE_REQUEST_CODE && resultCode == Activity.RESULT_OK) { - if(data != null) { - Uri uri = data.getData(); - if (uri != null) { - installPluginFromUri(uri, false); - } - } - } - } - - private String installPluginFile(File pluginFile, boolean force) throws IOException{ - int i = JamiService.installPlugin(pluginFile.getAbsolutePath(), force); - if (!pluginFile.delete()) { - Log.e(TAG,"Plugin Jpl file in the cache not freed"); - } - switch (i) { - case 0: - return pluginFile.getName(); - case 100: - throw new IOException(getResources() - .getString(R.string.plugin_same_version_exception, - pluginFile.getName())); - case 200: - throw new IOException(getResources() - .getString(R.string.plugin_recent_version_exception, - pluginFile.getName())); - default: - throw new IOException(getResources() - .getString(R.string.plugin_install_failure, - pluginFile.getName())); - } - } - - private void installPluginFromUri(Uri uri, boolean force) { - mCompositeDisposable.add(AndroidFileUtils.getCacheFile(requireContext(), uri) - .observeOn(AndroidSchedulers.mainThread()) - .map(file -> installPluginFile(file, force)) - .subscribe(filename -> { - String[] plugin = filename.split(".jpl"); - List<PluginDetails> availablePlugins = PluginUtils.getInstalledPlugins(requireContext()); - for (PluginDetails availablePlugin : availablePlugins) { - if (availablePlugin.getName().equals(plugin[0])) { - availablePlugin.setEnabled(true); - onPluginEnabled(availablePlugin); - } - } - mAdapter.updatePluginsList(PluginUtils.getInstalledPlugins(requireContext())); - Toast.makeText(requireContext(), "Plugin: " + filename + " successfully installed", Toast.LENGTH_LONG).show(); - mCompositeDisposable.dispose(); - }, e -> { - if (binding != null) { - Snackbar sb = Snackbar.make(binding.listLayout, "" + e.getMessage(), Snackbar.LENGTH_LONG); - sb.setAction(R.string.plugin_force_install, v -> installPluginFromUri(uri, true)); - sb.show(); - } - })); - } - - @Override - public void onDestroyView() { - binding = null; - super.onDestroyView(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - mCompositeDisposable.dispose(); - } -} - diff --git a/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListSettingsFragment.kt b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListSettingsFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..2e69adc15b590391649485a98ead95576efd358b --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/settings/pluginssettings/PluginsListSettingsFragment.kt @@ -0,0 +1,187 @@ +package cx.ring.settings.pluginssettings + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.activity.OnBackPressedCallback +import androidx.fragment.app.Fragment +import com.google.android.material.snackbar.Snackbar +import cx.ring.R +import cx.ring.account.JamiAccountSummaryFragment +import cx.ring.client.HomeActivity +import cx.ring.databinding.FragPluginsListSettingsBinding +import cx.ring.plugins.PluginUtils.getInstalledPlugins +import cx.ring.plugins.PluginUtils.loadPlugin +import cx.ring.plugins.PluginUtils.unloadPlugin +import cx.ring.settings.pluginssettings.PluginsListAdapter.PluginListItemListener +import cx.ring.utils.AndroidFileUtils.getCacheFile +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.disposables.CompositeDisposable +import net.jami.daemon.JamiService +import java.io.File +import java.io.IOException + +class PluginsListSettingsFragment : Fragment(), PluginListItemListener { + private val mOnBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(false) { + override fun handleOnBackPressed() { + this.isEnabled = false + val fragment = parentFragment as JamiAccountSummaryFragment? + fragment?.popBackStack() + } + } + private var binding: FragPluginsListSettingsBinding? = null + private var mAdapter: PluginsListAdapter? = null + private val mCompositeDisposable = CompositeDisposable() + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragPluginsListSettingsBinding.inflate(inflater, container, false) + + // use this setting to improve performance if you know that changes + // in content do not change the layout size of the RecyclerView + binding!!.pluginsList.setHasFixedSize(true) + + // specify an adapter (see also next example) + mAdapter = PluginsListAdapter(getInstalledPlugins(binding!!.pluginsList.context), this) + binding!!.pluginsList.adapter = mAdapter + + //Fab + binding!!.pluginsListSettingsFab.setOnClickListener { view: View? -> + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) + intent.addCategory(Intent.CATEGORY_OPENABLE) + intent.type = "*/*" + startActivityForResult(intent, ARCHIVE_REQUEST_CODE) + } + return binding!!.root + } + + override fun onResume() { + mOnBackPressedCallback.isEnabled = true + super.onResume() + } + + override fun onAttach(context: Context) { + super.onAttach(context) + requireActivity().onBackPressedDispatcher.addCallback(this, mOnBackPressedCallback) + } + + /** + * Implements PluginListItemListener.onPluginItemClicked which is called when we click on + * a plugin list item + * @param pluginDetails instance of a plugin details that is sent to PluginSettingsFragment + */ + override fun onPluginItemClicked(pluginDetails: PluginDetails) { + (requireActivity() as HomeActivity).gotToPluginSettings(pluginDetails) + } + + /** + * Implements PluginListItemListener.onPluginEnabled which is called when the checkbox + * associated with the plugin list item is called + * @param pluginDetails instance of a plugin details that is sent to PluginSettingsFragment + */ + override fun onPluginEnabled(pluginDetails: PluginDetails) { + if (pluginDetails.isEnabled) { + pluginDetails.isEnabled = loadPlugin(pluginDetails.rootPath) + } else { + unloadPlugin(pluginDetails.rootPath) + } + val status = if (pluginDetails.isEnabled) "ON" else "OFF" + Toast.makeText( + requireContext(), pluginDetails.name + " " + status, + Toast.LENGTH_SHORT + ).show() + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + if (requestCode == ARCHIVE_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + if (data != null) { + val uri = data.data + if (uri != null) { + installPluginFromUri(uri, false) + } + } + } + } + + @Throws(IOException::class) + private fun installPluginFile(pluginFile: File, force: Boolean): String { + val i = JamiService.installPlugin(pluginFile.absolutePath, force) + if (!pluginFile.delete()) { + Log.e(TAG, "Plugin Jpl file in the cache not freed") + } + return when (i) { + 0 -> pluginFile.name + 100 -> throw IOException( + resources + .getString( + R.string.plugin_same_version_exception, + pluginFile.name + ) + ) + 200 -> throw IOException( + resources + .getString( + R.string.plugin_recent_version_exception, + pluginFile.name + ) + ) + else -> throw IOException( + resources + .getString( + R.string.plugin_install_failure, + pluginFile.name + ) + ) + } + } + + private fun installPluginFromUri(uri: Uri, force: Boolean) { + mCompositeDisposable.add( + getCacheFile(requireContext(), uri) + .observeOn(AndroidSchedulers.mainThread()) + .map { file: File -> installPluginFile(file, force) } + .subscribe({ filename: String -> + val plugin = filename.split(".jpl".toRegex()).toTypedArray() + val availablePlugins = getInstalledPlugins(requireContext()) + for (availablePlugin in availablePlugins) { + if (availablePlugin.name == plugin[0]) { + availablePlugin.isEnabled = true + onPluginEnabled(availablePlugin) + } + } + mAdapter!!.updatePluginsList(getInstalledPlugins(requireContext())) + Toast.makeText(requireContext(), "Plugin: $filename successfully installed", Toast.LENGTH_LONG) + .show() + mCompositeDisposable.dispose() + }) { e: Throwable -> + if (binding != null) { + val sb = Snackbar.make(binding!!.listLayout, "" + e.message, Snackbar.LENGTH_LONG) + sb.setAction(R.string.plugin_force_install) { v: View? -> installPluginFromUri(uri, true) } + sb.show() + } + }) + } + + override fun onDestroyView() { + binding = null + super.onDestroyView() + } + + override fun onDestroy() { + super.onDestroy() + mCompositeDisposable.dispose() + } + + companion object { + val TAG = PluginsListSettingsFragment::class.java.simpleName + private const val ARCHIVE_REQUEST_CODE = 42 + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/share/ShareFragment.java b/ring-android/app/src/main/java/cx/ring/share/ShareFragment.java deleted file mode 100644 index 997bb7dec873f11f4976a4a4ce170e2b18448afe..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/share/ShareFragment.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com> - * Alexandre Lision <alexandre.lision@savoirfairelinux.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 cx.ring.share; - -import android.content.Intent; -import android.graphics.Bitmap; -import android.os.Bundle; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import net.jami.mvp.GenericView; -import net.jami.share.SharePresenter; -import net.jami.share.ShareViewModel; -import net.jami.utils.QRCodeUtils; - -import cx.ring.R; -import cx.ring.databinding.FragShareBinding; -import cx.ring.mvp.BaseSupportFragment; -import dagger.hilt.android.AndroidEntryPoint; - -@AndroidEntryPoint -public class ShareFragment extends BaseSupportFragment<SharePresenter, GenericView<ShareViewModel>> implements GenericView<ShareViewModel> { - - private String mUriToShow; - private boolean isShareLocked = false; - private FragShareBinding binding; - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - binding = FragShareBinding.inflate(inflater, container, false); - return binding.getRoot(); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - binding = null; - } - - @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - setHasOptionsMenu(true); - binding.shareButton.setOnClickListener(v -> { - if (!isShareLocked) shareAccount(); - }); - } - - private void shareAccount() { - if (!TextUtils.isEmpty(mUriToShow)) { - Intent sharingIntent = new Intent(Intent.ACTION_SEND); - sharingIntent.setType("text/plain"); - sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getText(R.string.account_contact_me)); - sharingIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.account_share_body, mUriToShow, getText(R.string.app_website))); - startActivity(Intent.createChooser(sharingIntent, getText(R.string.share_via))); - } - } - - @Override - public void showViewModel(final ShareViewModel viewModel) { - if (binding == null) - return; - - final QRCodeUtils.QRCodeData qrCodeData = viewModel.getAccountQRCodeData( - getResources().getColor(R.color.color_primary_dark), getResources().getColor(R.color.transparent)); - if (qrCodeData == null) { - binding.qrImage.setVisibility(View.INVISIBLE); - binding.shareInstruction.setText(R.string.share_message_no_account); - } else { - Bitmap bitmap = Bitmap.createBitmap(qrCodeData.getWidth(), qrCodeData.getHeight(), Bitmap.Config.ARGB_8888); - bitmap.setPixels(qrCodeData.getData(), 0, qrCodeData.getWidth(), 0, 0, qrCodeData.getWidth(), qrCodeData.getHeight()); - binding.qrImage.setImageBitmap(bitmap); - binding.shareInstruction.setText(R.string.share_message); - binding.qrImage.setVisibility(View.VISIBLE); - } - - mUriToShow = viewModel.getAccountDisplayUri(); - isShareLocked = TextUtils.isEmpty(mUriToShow); - } -} diff --git a/ring-android/app/src/main/java/cx/ring/share/ShareFragment.kt b/ring-android/app/src/main/java/cx/ring/share/ShareFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..dbaf9b3d9844ed8869b536bbcab5b20777ba6a0a --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/share/ShareFragment.kt @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Authors: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * Alexandre Lision <alexandre.lision@savoirfairelinux.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 cx.ring.share + +import android.content.Intent +import android.graphics.Bitmap +import android.os.Bundle +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import cx.ring.R +import cx.ring.databinding.FragShareBinding +import cx.ring.mvp.BaseSupportFragment +import dagger.hilt.android.AndroidEntryPoint +import net.jami.mvp.GenericView +import net.jami.share.SharePresenter +import net.jami.share.ShareViewModel + +@AndroidEntryPoint +class ShareFragment : BaseSupportFragment<SharePresenter, GenericView<ShareViewModel>>(), GenericView<ShareViewModel> { + private var mUriToShow: String? = null + private var isShareLocked = false + private var binding: FragShareBinding? = null + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = FragShareBinding.inflate(inflater, container, false) + return binding!!.root + } + + override fun onDestroyView() { + super.onDestroyView() + binding = null + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setHasOptionsMenu(true) + binding!!.shareButton.setOnClickListener { if (!isShareLocked) shareAccount() } + } + + private fun shareAccount() { + if (!TextUtils.isEmpty(mUriToShow)) { + val sharingIntent = Intent(Intent.ACTION_SEND) + sharingIntent.type = "text/plain" + sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getText(R.string.account_contact_me)) + sharingIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.account_share_body, mUriToShow, getText(R.string.app_website))) + startActivity(Intent.createChooser(sharingIntent, getText(R.string.share_via))) + } + } + + override fun showViewModel(viewModel: ShareViewModel) { + if (binding == null) return + val qrCodeData = viewModel.getAccountQRCodeData(resources.getColor(R.color.color_primary_dark), resources.getColor(R.color.transparent)) + if (qrCodeData == null) { + binding!!.qrImage.visibility = View.INVISIBLE + binding!!.shareInstruction.setText(R.string.share_message_no_account) + } else { + val bitmap = Bitmap.createBitmap(qrCodeData.width, qrCodeData.height, Bitmap.Config.ARGB_8888) + bitmap.setPixels(qrCodeData.data, 0, qrCodeData.width, 0, 0, qrCodeData.width, qrCodeData.height) + binding!!.qrImage.setImageBitmap(bitmap) + binding!!.shareInstruction.setText(R.string.share_message) + binding!!.qrImage.visibility = View.VISIBLE + } + mUriToShow = viewModel.accountDisplayUri + isShareLocked = TextUtils.isEmpty(mUriToShow) + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/tv/about/AboutDetailsFragment.java b/ring-android/app/src/main/java/cx/ring/tv/about/AboutDetailsFragment.java index 7207eabf53e4df2f1eefd4b5f2d95c6f2301cd3f..f07f4646a209fe461e8ecbc7376f3d5b038ef45d 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/about/AboutDetailsFragment.java +++ b/ring-android/app/src/main/java/cx/ring/tv/about/AboutDetailsFragment.java @@ -30,6 +30,8 @@ import androidx.leanback.widget.FullWidthDetailsOverviewRowPresenter; import androidx.leanback.widget.ListRow; import androidx.leanback.widget.ListRowPresenter; import androidx.leanback.widget.RowPresenter; + +import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -37,7 +39,6 @@ import cx.ring.R; import cx.ring.tv.cards.Card; import cx.ring.tv.cards.iconcards.IconCard; import cx.ring.tv.cards.iconcards.IconCardHelper; -import net.jami.utils.Log; public class AboutDetailsFragment extends DetailsSupportFragment { private static final String TAG = "AboutDetailsFragment"; diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/JamiPreferenceFragment.java b/ring-android/app/src/main/java/cx/ring/tv/account/JamiPreferenceFragment.java index 10d98bce5a4a2385503ea0f5e40b3922ecc51d3b..352d5a20687f16ff8aa50aa576bf4950d4ae2b86 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/account/JamiPreferenceFragment.java +++ b/ring-android/app/src/main/java/cx/ring/tv/account/JamiPreferenceFragment.java @@ -22,6 +22,7 @@ package cx.ring.tv.account; import android.os.Bundle; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.leanback.preference.LeanbackPreferenceFragmentCompat; import android.view.View; @@ -38,7 +39,7 @@ public abstract class JamiPreferenceFragment<T extends RootPresenter> extends Le protected T presenter; @Override - public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); //Be sure to do the injection in onCreateView method diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt b/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt index f5ecb658394121cd36901a881c0ee9d596d61e7f..c585b8e752cc9a54d6c55b263e746d53aafd73cd 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountExport.kt @@ -39,6 +39,7 @@ import dagger.hilt.android.AndroidEntryPoint import net.jami.account.JamiAccountSummaryPresenter import net.jami.account.JamiAccountSummaryView import net.jami.model.Account +import net.jami.model.Profile import java.io.File @AndroidEntryPoint @@ -52,7 +53,7 @@ class TVAccountExport : JamiGuidedStepFragment<JamiAccountSummaryPresenter>(), J presenter.setAccountId(mIdAccount) } - override fun onCreateGuidance(savedInstanceState: Bundle): Guidance { + override fun onCreateGuidance(savedInstanceState: Bundle?): Guidance { val title = getString(R.string.account_export_title) val breadcrumb = "" val description = getString(R.string.account_link_export_info_light) @@ -60,7 +61,7 @@ class TVAccountExport : JamiGuidedStepFragment<JamiAccountSummaryPresenter>(), J return Guidance(title, description, breadcrumb, icon) } - override fun onCreateActions(actions: List<GuidedAction>, savedInstanceState: Bundle) { + override fun onCreateActions(actions: List<GuidedAction>, savedInstanceState: Bundle?) { if (mHasPassword) { addPasswordAction(activity, actions, PASSWORD, getString(R.string.account_enter_password), "", "") } else { @@ -89,7 +90,7 @@ class TVAccountExport : JamiGuidedStepFragment<JamiAccountSummaryPresenter>(), J } override fun showPasswordProgressDialog() {} - override fun accountChanged(account: Account) {} + override fun accountChanged(account: Account, profile: Profile) {} override fun showNetworkError() { mWaitDialog!!.dismiss() AlertDialog.Builder(activity) @@ -164,7 +165,7 @@ class TVAccountExport : JamiGuidedStepFragment<JamiAccountSummaryPresenter>(), J override fun askCameraPermission() {} override fun goToGallery() {} override fun askGalleryPermission() {} - override fun updateUserView(account: Account) {} + override fun updateUserView(account: Account, profile: Profile) {} override fun goToMedia(accountId: String) {} override fun goToSystem(accountId: String) {} override fun goToAdvanced(accountId: String) {} diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.kt b/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.kt index 00a9cb5c35f8e5acd12d270f45a8e0b48a9cbdf7..a46291a09d1458b26a62a8f054d919bf7fba70c4 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVAccountWizard.kt @@ -28,6 +28,7 @@ import cx.ring.R import cx.ring.account.AccountCreationModelImpl import cx.ring.application.JamiApplication import cx.ring.mvp.BaseActivity +import cx.ring.services.VCardServiceImpl import dagger.hilt.android.AndroidEntryPoint import ezvcard.VCard import io.reactivex.rxjava3.core.Single @@ -36,11 +37,11 @@ import net.jami.account.AccountWizardPresenter import net.jami.account.AccountWizardView import net.jami.model.Account import net.jami.model.AccountConfig -import net.jami.mvp.AccountCreationModel +import net.jami.model.AccountCreationModel import net.jami.utils.VCardUtils @AndroidEntryPoint -class TVAccountWizard : BaseActivity<AccountWizardPresenter?>(), AccountWizardView { +class TVAccountWizard : BaseActivity<AccountWizardPresenter>(), AccountWizardView { private var mProgress: ProgressDialog? = null private var mLinkAccount = false private var mAccountType: String? = null @@ -57,15 +58,11 @@ class TVAccountWizard : BaseActivity<AccountWizardPresenter?>(), AccountWizardVi mAccountType = AccountConfig.ACCOUNT_TYPE_RING } if (savedInstanceState == null) { - GuidedStepSupportFragment.addAsRoot( - this, - TVHomeAccountCreationFragment(), - android.R.id.content - ) + GuidedStepSupportFragment.addAsRoot(this, TVHomeAccountCreationFragment(), android.R.id.content) } else { mLinkAccount = savedInstanceState.getBoolean("mLinkAccount") } - presenter!!.init(if (getIntent().action != null) getIntent().action else AccountConfig.ACCOUNT_TYPE_RING) + presenter.init(getIntent().action ?: AccountConfig.ACCOUNT_TYPE_RING) } override fun onSaveInstanceState(outState: Bundle) { @@ -74,8 +71,8 @@ class TVAccountWizard : BaseActivity<AccountWizardPresenter?>(), AccountWizardVi } override fun onDestroy() { - if (mProgress != null) { - mProgress!!.dismiss() + mProgress?.let { progress -> + progress.dismiss() mProgress = null } super.onDestroy() @@ -83,9 +80,9 @@ class TVAccountWizard : BaseActivity<AccountWizardPresenter?>(), AccountWizardVi fun createAccount(accountCreationModel: AccountCreationModel) { if (accountCreationModel.isLink) { - presenter!!.initJamiAccountLink(accountCreationModel, getText(R.string.ring_account_default_name).toString()) + presenter.initJamiAccountLink(accountCreationModel, getText(R.string.ring_account_default_name).toString()) } else { - presenter!!.initJamiAccountCreation(accountCreationModel, getText(R.string.ring_account_default_name).toString()) + presenter.initJamiAccountCreation(accountCreationModel, getText(R.string.ring_account_default_name).toString()) } } @@ -97,10 +94,7 @@ class TVAccountWizard : BaseActivity<AccountWizardPresenter?>(), AccountWizardVi } override fun goToProfileCreation(accountCreationModel: AccountCreationModel) { - GuidedStepSupportFragment.add( - supportFragmentManager, - TVProfileCreationFragment.newInstance(accountCreationModel as AccountCreationModelImpl) - ) + GuidedStepSupportFragment.add(supportFragmentManager, TVProfileCreationFragment.newInstance(accountCreationModel as AccountCreationModelImpl)) } override fun displayProgress(display: Boolean) { @@ -113,10 +107,9 @@ class TVAccountWizard : BaseActivity<AccountWizardPresenter?>(), AccountWizardVi show() } } else { - if (mProgress != null) { - if (mProgress!!.isShowing) { - mProgress!!.dismiss() - } + mProgress?.let { progress -> + if (progress.isShowing) + progress.dismiss() mProgress = null } } @@ -147,7 +140,7 @@ class TVAccountWizard : BaseActivity<AccountWizardPresenter?>(), AccountWizardVi val filedir = filesDir return accountCreationModel.toVCard() .flatMap { vcard -> - account.resetProfile() + account.loadedProfile = Single.fromCallable { VCardServiceImpl.readData(vcard) }.cache() VCardUtils.saveLocalProfileToDisk(vcard, account.accountID, filedir) } .subscribeOn(Schedulers.io()) @@ -195,7 +188,7 @@ class TVAccountWizard : BaseActivity<AccountWizardPresenter?>(), AccountWizardVi finish() } - fun profileCreated(accountCreationModel: AccountCreationModel?, saveProfile: Boolean) { - presenter!!.profileCreated(accountCreationModel, saveProfile) + fun profileCreated(accountCreationModel: AccountCreationModel, saveProfile: Boolean) { + presenter.profileCreated(accountCreationModel, saveProfile) } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiAccountCreationFragment.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiAccountCreationFragment.java index 7b1bd5c574a4c995b3a5fd72d2275ee29830426b..9477515142b3c108147f2f17ace2133c5e6b139d 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiAccountCreationFragment.java +++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiAccountCreationFragment.java @@ -25,6 +25,7 @@ import androidx.leanback.widget.GuidanceStylist; import androidx.leanback.widget.GuidedAction; import android.text.Editable; import android.text.TextWatcher; +import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; @@ -35,11 +36,9 @@ import cx.ring.R; import cx.ring.account.AccountCreationModelImpl; import net.jami.account.JamiAccountCreationPresenter; import net.jami.account.JamiAccountCreationView; -import cx.ring.application.JamiApplication; import dagger.hilt.android.AndroidEntryPoint; -import net.jami.mvp.AccountCreationModel; -import net.jami.utils.Log; +import net.jami.model.AccountCreationModel; import net.jami.utils.StringUtils; @AndroidEntryPoint diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiLinkAccountFragment.java b/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiLinkAccountFragment.java index 5ff65dcc195c7694d26fba6881ad957ae598fdb5..6bdbebc686d2e49662c67e0fa031c0f71641e9f9 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiLinkAccountFragment.java +++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVJamiLinkAccountFragment.java @@ -18,6 +18,7 @@ package cx.ring.tv.account; import android.app.Activity; +import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.os.Bundle; import androidx.annotation.NonNull; @@ -31,10 +32,10 @@ import cx.ring.R; import cx.ring.account.AccountCreationModelImpl; import net.jami.account.JamiLinkAccountPresenter; import net.jami.account.JamiLinkAccountView; -import cx.ring.application.JamiApplication; + import dagger.hilt.android.AndroidEntryPoint; -import net.jami.mvp.AccountCreationModel; +import net.jami.model.AccountCreationModel; import net.jami.utils.StringUtils; @AndroidEntryPoint @@ -59,7 +60,7 @@ public class TVJamiLinkAccountFragment extends JamiGuidedStepFragment<JamiLinkAc super.onViewCreated(view, savedInstanceState); presenter.init(model); if (model != null && model.getPhoto() != null) { - getGuidanceStylist().getIconView().setImageBitmap(model.getPhoto()); + getGuidanceStylist().getIconView().setImageBitmap((Bitmap) model.getPhoto()); } } @@ -105,7 +106,7 @@ public class TVJamiLinkAccountFragment extends JamiGuidedStepFragment<JamiLinkAc } @Override - public void createAccount(AccountCreationModel accountCreationModel) { + public void createAccount(@NonNull AccountCreationModel accountCreationModel) { ((TVAccountWizard) getActivity()).createAccount(accountCreationModel); } diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.kt index 4d1e32a33e457523f7a2159208204c399a980f35..080c5faf75203a8d1ee4f4af4a2a8538f6cb61db 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileCreationFragment.kt @@ -45,7 +45,7 @@ import dagger.hilt.android.AndroidEntryPoint import io.reactivex.rxjava3.core.Single import net.jami.account.ProfileCreationPresenter import net.jami.account.ProfileCreationView -import net.jami.mvp.AccountCreationModel +import net.jami.model.AccountCreationModel @AndroidEntryPoint class TVProfileCreationFragment : JamiGuidedStepFragment<ProfileCreationPresenter>(), @@ -59,14 +59,14 @@ class TVProfileCreationFragment : JamiGuidedStepFragment<ProfileCreationPresente try { requireContext().contentResolver.openInputStream(uri!!).use { iStream -> val image = BitmapFactory.decodeStream(iStream) - presenter!!.photoUpdated(Single.just(intent).map { image }) + presenter.photoUpdated(Single.just(intent).map { image }) } } catch (e: Exception) { e.printStackTrace() } } ProfileCreationFragment.REQUEST_CODE_GALLERY -> if (resultCode == Activity.RESULT_OK && intent != null) { - presenter!!.photoUpdated( + presenter.photoUpdated( loadBitmap(requireContext(), intent.data!!).map { b: Bitmap? -> b }) } else -> super.onActivityResult(requestCode, resultCode, intent) @@ -76,11 +76,11 @@ class TVProfileCreationFragment : JamiGuidedStepFragment<ProfileCreationPresente override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { when (requestCode) { ProfileCreationFragment.REQUEST_PERMISSION_CAMERA -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - presenter!!.cameraPermissionChanged(true) - presenter!!.cameraClick() + presenter.cameraPermissionChanged(true) + presenter.cameraClick() } ProfileCreationFragment.REQUEST_PERMISSION_READ_STORAGE -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - presenter!!.galleryClick() + presenter.galleryClick() } } } @@ -92,10 +92,10 @@ class TVProfileCreationFragment : JamiGuidedStepFragment<ProfileCreationPresente return } iconSize = resources.getDimension(R.dimen.tv_avatar_size).toInt() - presenter!!.initPresenter(mModel) + presenter.initPresenter(mModel!!) } - override fun onCreateGuidance(savedInstanceState: Bundle): Guidance { + override fun onCreateGuidance(savedInstanceState: Bundle?): Guidance { val title = getString(R.string.account_create_title) val breadcrumb = "" val description = getString(R.string.profile_message_warning) @@ -113,7 +113,7 @@ class TVProfileCreationFragment : JamiGuidedStepFragment<ProfileCreationPresente return R.style.Theme_Ring_Leanback_GuidedStep_First } - override fun onCreateActions(actions: List<GuidedAction>, savedInstanceState: Bundle) { + override fun onCreateActions(actions: List<GuidedAction>, savedInstanceState: Bundle?) { addEditTextAction(activity, actions, USER_NAME.toLong(), R.string.profile_name_hint, R.string.profile_name_hint) addAction(activity, actions, CAMERA.toLong(), getString(R.string.take_a_photo), "") addAction(activity, actions, GALLERY.toLong(), getString(R.string.open_the_gallery), "") @@ -122,9 +122,9 @@ class TVProfileCreationFragment : JamiGuidedStepFragment<ProfileCreationPresente override fun onGuidedActionClicked(action: GuidedAction) { when (action.id) { - CAMERA.toLong() -> presenter!!.cameraClick() - GALLERY.toLong() -> presenter!!.galleryClick() - NEXT.toLong() -> presenter!!.nextClick() + CAMERA.toLong() -> presenter.cameraClick() + GALLERY.toLong() -> presenter.galleryClick() + NEXT.toLong() -> presenter.nextClick() } } @@ -176,8 +176,8 @@ class TVProfileCreationFragment : JamiGuidedStepFragment<ProfileCreationPresente val model = accountCreationModel as AccountCreationModelImpl val newAccount = model.newAccount val avatar = AvatarDrawable.Builder() - .withPhoto(model.photo) - .withNameData(accountCreationModel.getFullName(), accountCreationModel.getUsername()) + .withPhoto(model.photo as Bitmap?) + .withNameData(accountCreationModel.fullName, accountCreationModel.username) .withId(newAccount?.username) .withCircleCrop(true) .build(requireContext()) @@ -189,12 +189,12 @@ class TVProfileCreationFragment : JamiGuidedStepFragment<ProfileCreationPresente when (action.id.toInt()) { USER_NAME -> { val username = action.editTitle.toString() - presenter!!.fullNameUpdated(username) + presenter.fullNameUpdated(username) if (username.isEmpty()) action.title = getString(R.string.profile_name_hint) else action.title = username } - CAMERA -> presenter!!.cameraClick() - GALLERY -> presenter!!.galleryClick() + CAMERA -> presenter.cameraClick() + GALLERY -> presenter.galleryClick() } return super.onGuidedActionEditedAndProceed(action) } @@ -202,7 +202,7 @@ class TVProfileCreationFragment : JamiGuidedStepFragment<ProfileCreationPresente override fun onGuidedActionEditCanceled(action: GuidedAction) { if (action.id.toInt() == USER_NAME) { val username = action.editTitle.toString() - presenter!!.fullNameUpdated(username) + presenter.fullNameUpdated(username) if (TextUtils.isEmpty(username)) action.title = getString(R.string.profile_name_hint) else action.title = username } diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.kt index 7042d907a04e5efbb4e4a3cdfc6e34df727807e9..d6c129613183bb6f34af22e315bdea9317e7d41b 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVProfileEditingFragment.kt @@ -25,6 +25,7 @@ import android.content.Intent import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.drawable.Drawable import android.net.Uri import android.os.Bundle import android.provider.MediaStore @@ -36,10 +37,9 @@ import androidx.leanback.widget.GuidedAction import cx.ring.R import cx.ring.account.ProfileCreationFragment import cx.ring.tv.camera.CustomCameraActivity -import cx.ring.utils.AndroidFileUtils.loadBitmap +import cx.ring.utils.AndroidFileUtils import cx.ring.utils.BitmapUtils import cx.ring.views.AvatarDrawable -import cx.ring.views.AvatarDrawable.Companion.load import dagger.hilt.android.AndroidEntryPoint import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Single @@ -69,7 +69,7 @@ class TVProfileEditingFragment : JamiGuidedStepFragment<HomeNavigationPresenter> } } ProfileCreationFragment.REQUEST_CODE_GALLERY -> if (resultCode == Activity.RESULT_OK && data != null) { - presenter.saveVCardPhoto(loadBitmap(requireContext(), data.data!!) + presenter.saveVCardPhoto(AndroidFileUtils.loadBitmap(requireContext(), data.data!!) .map { obj: Bitmap -> BitmapUtils.bitmapToPhoto(obj) }) } else -> { @@ -80,11 +80,11 @@ class TVProfileEditingFragment : JamiGuidedStepFragment<HomeNavigationPresenter> override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { when (requestCode) { ProfileCreationFragment.REQUEST_PERMISSION_CAMERA -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - presenter!!.cameraPermissionChanged(true) - presenter!!.cameraClicked() + presenter.cameraPermissionChanged(true) + presenter.cameraClicked() } ProfileCreationFragment.REQUEST_PERMISSION_READ_STORAGE -> if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - presenter!!.galleryClicked() + presenter.galleryClicked() } } } @@ -131,31 +131,23 @@ class TVProfileEditingFragment : JamiGuidedStepFragment<HomeNavigationPresenter> override fun showViewModel(viewModel: HomeNavigationViewModel) { val action = actions?.let { if (it.isEmpty()) null else it[0] } - if (action != null && action.id == USER_NAME.toLong()) { - if (TextUtils.isEmpty(viewModel.alias)) { + if (action != null && action.id == USER_NAME) { + if (TextUtils.isEmpty(viewModel.profile.displayName)) { action.editTitle = "" action.title = getString(R.string.account_edit_profile) } else { - action.editTitle = viewModel.alias - action.title = viewModel.alias + action.editTitle = viewModel.profile.displayName + action.title = viewModel.profile.displayName } notifyActionChanged(0) } - if (TextUtils.isEmpty(viewModel.alias)) + if (TextUtils.isEmpty(viewModel.profile.displayName)) guidanceStylist.titleView.setText(R.string.profile) - else guidanceStylist.titleView.text = viewModel.alias - setPhoto(viewModel.account) + else guidanceStylist.titleView.text = viewModel.profile.displayName + guidanceStylist.iconView.setImageDrawable(AvatarDrawable.build(requireContext(), viewModel.account, viewModel.profile, true) + .apply { setInSize(iconSize) }) } - override fun setPhoto(account: Account) { - load(requireContext(), account) - .map { avatar -> avatar.apply { setInSize(iconSize) } } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { avatar: AvatarDrawable? -> guidanceStylist.iconView.setImageDrawable(avatar) } - } - - override fun updateModel(account: Account) {} override fun gotToImageCapture() { val intent = Intent(activity, CustomCameraActivity::class.java) startActivityForResult(intent, ProfileCreationFragment.REQUEST_CODE_PHOTO) diff --git a/ring-android/app/src/main/java/cx/ring/tv/account/TVShareFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/account/TVShareFragment.kt index b7b86871950c99d0ecf0d41156e27a0802d3fcd0..7efb23299a79ccc8bfa10258c15c6cdfc07c6c8b 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/account/TVShareFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/account/TVShareFragment.kt @@ -21,15 +21,15 @@ package cx.ring.tv.account import android.graphics.Bitmap import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import cx.ring.R import cx.ring.databinding.TvFragShareBinding import cx.ring.mvp.BaseSupportFragment -import cx.ring.services.VCardServiceImpl.Companion.loadProfile +import cx.ring.services.VCardServiceImpl import cx.ring.views.AvatarDrawable -import cx.ring.views.AvatarDrawable.Companion.build import dagger.hilt.android.AndroidEntryPoint import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.disposables.CompositeDisposable @@ -37,7 +37,6 @@ import net.jami.model.Account import net.jami.mvp.GenericView import net.jami.share.SharePresenter import net.jami.share.ShareViewModel -import net.jami.utils.Log @AndroidEntryPoint class TVShareFragment : BaseSupportFragment<SharePresenter, GenericView<ShareViewModel>>(), GenericView<ShareViewModel> { @@ -77,7 +76,7 @@ class TVShareFragment : BaseSupportFragment<SharePresenter, GenericView<ShareVie } private fun getUserAvatar(account: Account) { - disposable.add(loadProfile(requireContext(), account) + disposable.add(VCardServiceImpl.loadProfile(requireContext(), account) .observeOn(AndroidSchedulers.mainThread()) .doOnNext { binding?.apply { @@ -85,12 +84,12 @@ class TVShareFragment : BaseSupportFragment<SharePresenter, GenericView<ShareVie shareUri?.text = account.displayUsername } } - .map { p -> build(requireContext(), account, p, true) } - .subscribe({ a: AvatarDrawable? -> + .map { p -> AvatarDrawable.build(requireContext(), account, p, true) } + .subscribe({ a: AvatarDrawable -> binding?.apply { qrUserPhoto?.visibility = View.VISIBLE qrUserPhoto?.setImageDrawable(a) } - }) { e -> Log.e(TVShareFragment::class.simpleName, e.message) }) + }) { e -> Log.e(TVShareFragment::class.simpleName!!, e.message!!) }) } } diff --git a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.kt b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.kt index 3887e845d2cc7301104d617421d8e79f561eeac3..9c011f9c8a6aea2f27a65bad352bbde1e15db1a5 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallActivity.kt @@ -26,13 +26,13 @@ import android.os.Build import android.view.WindowManager import cx.ring.R import android.media.AudioManager +import android.util.Log import android.view.KeyEvent import net.jami.services.NotificationService import net.jami.call.CallView import android.view.MotionEvent import cx.ring.application.JamiApplication import cx.ring.utils.ConversationPath -import net.jami.utils.Log @AndroidEntryPoint class TVCallActivity : FragmentActivity() { diff --git a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.kt index 5fca0317ec40d1b75932f3674766b3fb4f04fa6a..a75ccb56bd72b08dab71da7c2bb94fb628fb0b56 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/call/TVCallFragment.kt @@ -117,8 +117,8 @@ class TVCallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView args.getString(CallFragment.KEY_ACTION)?.let { action -> if (action == CallFragment.ACTION_PLACE_CALL || action == Intent.ACTION_CALL) prepareCall(false) - else if (action == CallFragment.ACTION_GET_CALL || action == CallActivity.ACTION_CALL_ACCEPT) - presenter.initIncomingCall(args.getString(CallFragment.KEY_CONF_ID)!!, action == CallFragment.ACTION_GET_CALL) + else if (action == Intent.ACTION_VIEW || action == CallActivity.ACTION_CALL_ACCEPT) + presenter.initIncomingCall(args.getString(CallFragment.KEY_CONF_ID)!!, action == Intent.ACTION_VIEW) } } @@ -211,17 +211,12 @@ class TVCallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView if (mScreenWakeLock != null && mScreenWakeLock!!.isHeld) { mScreenWakeLock!!.release() } - val r = runnable - if (r != null) { + runnable?.let { r -> view?.handler?.removeCallbacks(r) } } - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array<String>, - grantResults: IntArray - ) { + override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode != REQUEST_PERMISSION_INCOMING && requestCode != REQUEST_PERMISSION_OUTGOING) return var i = 0 @@ -267,14 +262,9 @@ class TVCallFragment : BaseSupportFragment<CallPresenter, CallView>(), CallView binding!!.contactBubbleLayout.visibility = if (display) View.VISIBLE else View.GONE } - override fun displayVideoSurface( - displayVideoSurface: Boolean, - displayPreviewContainer: Boolean - ) { - binding!!.videoSurface.visibility = - if (displayVideoSurface) View.VISIBLE else View.GONE - binding!!.previewContainer.visibility = - if (displayPreviewContainer) View.VISIBLE else View.GONE + override fun displayVideoSurface(displayVideoSurface: Boolean, displayPreviewContainer: Boolean) { + binding!!.videoSurface.visibility = if (displayVideoSurface) View.VISIBLE else View.GONE + binding!!.previewContainer.visibility = if (displayPreviewContainer) View.VISIBLE else View.GONE } override fun displayPreviewSurface(display: Boolean) { diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCard.java b/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCard.java deleted file mode 100644 index af459b1739ed8ce34cafcad4c4740cee612241dd..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCard.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Loïc Siret <loic.siret@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.tv.cards.contacts; - -import net.jami.model.Contact; -import net.jami.smartlist.SmartListViewModel; -import cx.ring.tv.cards.Card; - -public class ContactCard extends Card { - private SmartListViewModel mModel; - - public ContactCard(String accountId, Contact pContact, Type type) { - mModel = new SmartListViewModel(accountId, pContact, null); - setId(pContact.getId()); - setTitle(pContact.getDisplayName()); - setDescription(pContact.getRingUsername()); - setType(type); - } - - public ContactCard(SmartListViewModel model) { - setModel(model); - } - - public void setModel(SmartListViewModel model) { - mModel = model; - setTitle(mModel.getContactName()); - String username = mModel.getContacts().get(0).getRingUsername(); - setDescription(username); - boolean isOnline = mModel.getContacts().get(0).isOnline(); - if (mModel.getContactName().equals(username)) { - if (isOnline) { - setType(Type.CONTACT_ONLINE); - } else { - setType(Type.CONTACT); - } - } else { - if (isOnline) { - setType(Type.CONTACT_WITH_USERNAME_ONLINE); - } else { - setType(Type.CONTACT_WITH_USERNAME); - } - } - } - - public SmartListViewModel getModel() { - return mModel; - } -} diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCard.kt b/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCard.kt new file mode 100644 index 0000000000000000000000000000000000000000..aa1aa0f2247bf3e556bafd4c51cb922337771bce --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCard.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Loïc Siret <loic.siret@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.tv.cards.contacts + +import cx.ring.tv.cards.Card +import net.jami.model.Contact +import net.jami.smartlist.SmartListViewModel + +class ContactCard : Card { + private var mModel: SmartListViewModel + + constructor(accountId: String, pContact: Contact, type: Type?) { + mModel = SmartListViewModel(accountId, pContact, null) + id = pContact.id + title = pContact.displayName + description = pContact.ringUsername + setType(type) + } + + constructor(m: SmartListViewModel) { + mModel = m + model = m + } + + var model: SmartListViewModel + get() = mModel + set(model) { + mModel = model + title = model.contactName + val contact = model.getContact()!! + val username = contact.ringUsername + description = username + val isOnline = contact.isOnline + type = if (model.contactName == username) { + if (isOnline) { + Type.CONTACT_ONLINE + } else { + Type.CONTACT + } + } else { + if (isOnline) { + Type.CONTACT_WITH_USERNAME_ONLINE + } else { + Type.CONTACT_WITH_USERNAME + } + } + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.java deleted file mode 100644 index c0d4e03367e83c793c1cce380a32edb6f92286cf..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Loïc Siret <loic.siret@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.tv.cards.contacts; - -import android.content.Context; - -import android.view.ContextThemeWrapper; - -import net.jami.smartlist.SmartListViewModel; - -import cx.ring.R; -import cx.ring.tv.cards.AbstractCardPresenter; -import cx.ring.tv.cards.Card; -import cx.ring.tv.cards.CardView; -import cx.ring.views.AvatarDrawable; - -public class ContactCardPresenter extends AbstractCardPresenter<CardView> { - - private static final String TAG = ContactCardPresenter.class.getSimpleName(); - - public ContactCardPresenter(Context context, int resId) { - super(new ContextThemeWrapper(context, resId)); - } - - @Override - protected CardView onCreateView() { - return new CardView(getContext()); - } - - @Override - public void onBindViewHolder(Card card, CardView cardView) { - ContactCard contact = (ContactCard) card; - - SmartListViewModel model = contact.getModel(); - cardView.setTitleText(card.getTitle()); - cardView.setContentText(card.getDescription()); - cardView.setTitleSingleLine(true); - cardView.setBackgroundColor(getContext().getResources().getColor(R.color.tv_transparent)); - cardView.setInfoAreaBackgroundColor(getContext().getResources().getColor(R.color.transparent)); - - cardView.setMainImage( - new AvatarDrawable.Builder() - .withViewModel(model) - .withPresence(false) - .withCircleCrop(false) - .build(getContext()) - ); - } -} diff --git a/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.kt b/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..775df92041adae90849f02e75bafeeb87daf10fe --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/tv/cards/contacts/ContactCardPresenter.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Loïc Siret <loic.siret@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.tv.cards.contacts + +import android.content.Context +import android.view.ContextThemeWrapper +import cx.ring.R +import cx.ring.tv.cards.AbstractCardPresenter +import cx.ring.tv.cards.Card +import cx.ring.tv.cards.CardView +import cx.ring.views.AvatarDrawable + +class ContactCardPresenter(context: Context, resId: Int) : + AbstractCardPresenter<CardView>(ContextThemeWrapper(context, resId)) { + override fun onCreateView(): CardView { + return CardView(context) + } + + override fun onBindViewHolder(card: Card, cardView: CardView) { + val contact = card as ContactCard + cardView.titleText = card.getTitle() + cardView.contentText = card.getDescription() + cardView.setTitleSingleLine(true) + cardView.setBackgroundColor(context.resources.getColor(R.color.tv_transparent)) + cardView.setInfoAreaBackgroundColor(context.resources.getColor(R.color.transparent)) + cardView.mainImage = AvatarDrawable.Builder() + .withViewModel(contact.model) + .withPresence(false) + .withCircleCrop(false) + .build(context) + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.kt index af1757d9f8cce376cc26ec7d4e1076df48fb248f..fb929dd52c54ee9ac164e2a255334db5d662c598 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactFragment.kt @@ -68,26 +68,18 @@ class TVContactFragment : BaseDetailFragment<TVContactPresenter>(), TVContactVie setupAdapter() val res = resources iconSize = res.getDimensionPixelSize(R.dimen.tv_avatar_size) - presenter!!.setContact(mConversationPath) + presenter.setContact(mConversationPath) } private fun setupAdapter() { // Set detail background and style. val detailsPresenter: FullWidthDetailsOverviewRowPresenter = if (isIncomingRequest || isOutgoingRequest) { - FullWidthDetailsOverviewRowPresenter( - TVContactRequestDetailPresenter(), - DetailsOverviewLogoPresenter() - ) + FullWidthDetailsOverviewRowPresenter(TVContactRequestDetailPresenter(),DetailsOverviewLogoPresenter()) } else { - FullWidthDetailsOverviewRowPresenter( - TVContactDetailPresenter(), - DetailsOverviewLogoPresenter() - ) + FullWidthDetailsOverviewRowPresenter(TVContactDetailPresenter(), DetailsOverviewLogoPresenter()) } - detailsPresenter.backgroundColor = - ContextCompat.getColor(requireContext(), R.color.tv_contact_background) - detailsPresenter.actionsBackgroundColor = - ContextCompat.getColor(requireContext(), R.color.tv_contact_row_background) + detailsPresenter.backgroundColor = ContextCompat.getColor(requireContext(), R.color.tv_contact_background) + detailsPresenter.actionsBackgroundColor = ContextCompat.getColor(requireContext(), R.color.tv_contact_row_background) detailsPresenter.initialState = FullWidthDetailsOverviewRowPresenter.STATE_HALF // Hook up transition element. @@ -101,24 +93,18 @@ class TVContactFragment : BaseDetailFragment<TVContactPresenter>(), TVContactVie } detailsPresenter.onActionClickedListener = OnActionClickedListener { action: Action -> if (action.id == ACTION_CALL.toLong()) { - presenter!!.contactClicked() + presenter.contactClicked() } else if (action.id == ACTION_ADD_CONTACT.toLong()) { - presenter!!.onAddContact() + presenter.onAddContact() } else if (action.id == ACTION_ACCEPT.toLong()) { - presenter!!.acceptTrustRequest() + presenter.acceptTrustRequest() } else if (action.id == ACTION_REFUSE.toLong()) { - presenter!!.refuseTrustRequest() + presenter.refuseTrustRequest() } else if (action.id == ACTION_BLOCK.toLong()) { - presenter!!.blockTrustRequest() + presenter.blockTrustRequest() } else if (action.id == ACTION_MORE.toLong()) { - startActivityForResult( - Intent(getActivity(), TVContactMoreActivity::class.java) - .setDataAndType( - mConversationPath!!.toUri(), - TVContactMoreActivity.CONTACT_REQUEST_URI - ), - REQUEST_CODE - ) + startActivityForResult(Intent(getActivity(), TVContactMoreActivity::class.java) + .setDataAndType(mConversationPath.toUri(), TVContactMoreActivity.CONTACT_REQUEST_URI), REQUEST_CODE) } } val mPresenterSelector = ClassPresenterSelector() @@ -161,30 +147,25 @@ class TVContactFragment : BaseDetailFragment<TVContactPresenter>(), TVContactVie } override fun callContact(accountId: String, conversationUri: Uri, uri: Uri) { - startActivity( - Intent(Intent.ACTION_CALL) - .setClass(requireContext(), TVCallActivity::class.java) - .putExtras(ConversationPath.toBundle(accountId, conversationUri)) - .putExtra(Intent.EXTRA_PHONE_NUMBER, uri.uri) - ) + startActivity(Intent(Intent.ACTION_CALL) + .setClass(requireContext(), TVCallActivity::class.java) + .putExtras(ConversationPath.toBundle(accountId, conversationUri)) + .putExtra(Intent.EXTRA_PHONE_NUMBER, uri.uri)) } override fun goToCallActivity(id: String) { - startActivity( - Intent(requireContext(), TVCallActivity::class.java) - .putExtra(NotificationService.KEY_CALL_ID, id) - ) + startActivity(Intent(requireContext(), TVCallActivity::class.java) + .putExtra(NotificationService.KEY_CALL_ID, id)) } override fun switchToConversationView() { isIncomingRequest = false isOutgoingRequest = false setupAdapter() - presenter!!.setContact(mConversationPath) + presenter.setContact(mConversationPath) } override fun finishView() { - val activity: Activity? = activity activity?.onBackPressed() } @@ -194,7 +175,6 @@ class TVContactFragment : BaseDetailFragment<TVContactPresenter>(), TVContactVie } companion object { - @JvmField val TAG = TVContactFragment::class.simpleName!! private const val ACTION_CALL = 0 private const val ACTION_ACCEPT = 1 diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.kt b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.kt index dea9074838d0923ec039cb923454ffc35ffdca98..5e2f9db48061b0847d6f7f7ca712d0c9d2b8c66c 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactPresenter.kt @@ -53,7 +53,7 @@ class TVContactPresenter @Inject constructor( .getAccountSubject(path.accountId) .map { a: Account -> SmartListViewModel(a.getByUri(mUri)!!, true) } .observeOn(mUiScheduler) - .subscribe { c: SmartListViewModel -> view!!.showContact(c) }) + .subscribe { c: SmartListViewModel -> view?.showContact(c) }) } fun contactClicked() { diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactView.java b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactView.java index d891d3bc08c8981f47df5eab2700d2adb5eb328f..5de1f86042fe65225f4309237c54437112ceb475 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactView.java +++ b/ring-android/app/src/main/java/cx/ring/tv/contact/TVContactView.java @@ -21,10 +21,9 @@ package cx.ring.tv.contact; import net.jami.model.Uri; -import net.jami.mvp.BaseView; import net.jami.smartlist.SmartListViewModel; -public interface TVContactView extends BaseView { +public interface TVContactView { void showContact(SmartListViewModel model); void callContact(String accountID, Uri conversationUri, Uri uri); diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreActivity.java b/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreActivity.kt similarity index 64% rename from ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreActivity.java rename to ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreActivity.kt index 40e8b2413db53441cc8bf7a89ae7445ad628c2f7..85525b730d1ac6d5bce8066909b60d1c4b8df357 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreActivity.java +++ b/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreActivity.kt @@ -17,24 +17,21 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +package cx.ring.tv.contact.more -package cx.ring.tv.contact.more; - -import android.os.Bundle; - -import androidx.fragment.app.FragmentActivity; - -import cx.ring.R; -import dagger.hilt.android.AndroidEntryPoint; +import dagger.hilt.android.AndroidEntryPoint +import androidx.fragment.app.FragmentActivity +import android.os.Bundle +import cx.ring.R @AndroidEntryPoint -public class TVContactMoreActivity extends FragmentActivity { - - public static final String CONTACT_REQUEST_URI = "uri"; +class TVContactMoreActivity : FragmentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.tv_activity_contact_more) + } - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.tv_activity_contact_more); + companion object { + const val CONTACT_REQUEST_URI = "uri" } -} +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreFragment.kt index e8996373d1e5b0f6cb06dca66eb7f6f417167230..cde26a27d818822e066446f42c1a9b257497c81b 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/contact/more/TVContactMoreFragment.kt @@ -40,29 +40,23 @@ class TVContactMoreFragment : LeanbackSettingsFragmentCompat() { startPreferenceFragment(PrefsFragment.newInstance()) } - override fun onPreferenceStartFragment( - preferenceFragment: PreferenceFragmentCompat, - preference: Preference - ): Boolean { + override fun onPreferenceStartFragment(preferenceFragment: PreferenceFragmentCompat, preference: Preference): Boolean { return false } - override fun onPreferenceStartScreen( - caller: PreferenceFragmentCompat, - pref: PreferenceScreen - ): Boolean { + override fun onPreferenceStartScreen(caller: PreferenceFragmentCompat, pref: PreferenceScreen): Boolean { return false } @AndroidEntryPoint class PrefsFragment : JamiPreferenceFragment<TVContactMorePresenter>(), TVContactMoreView { - override fun onCreatePreferences(savedInstanceState: Bundle, rootKey: String) { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.tv_contact_more_pref, rootKey) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - presenter!!.setContact(fromIntent(requireActivity().intent)) + presenter.setContact(fromIntent(requireActivity().intent)) } override fun onPreferenceTreeClick(preference: Preference): Boolean { @@ -70,12 +64,12 @@ class TVContactMoreFragment : LeanbackSettingsFragmentCompat() { createDialog( getString(R.string.conversation_action_history_clear_title), getString(R.string.clear_history) - ) { dialog: DialogInterface?, whichButton: Int -> presenter!!.clearHistory() } + ) { dialog: DialogInterface?, whichButton: Int -> presenter.clearHistory() } } else if (preference.key == "Contact.delete") { createDialog( getString(R.string.conversation_action_remove_this_title), getString(R.string.menu_delete) - ) { dialog: DialogInterface?, whichButton: Int -> presenter!!.removeContact() } + ) { dialog: DialogInterface?, whichButton: Int -> presenter.removeContact() } } return super.onPreferenceTreeClick(preference) } diff --git a/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.kt index 0ed5ceeb95caf4031417898a1c8f8149f60f27c5..d1e9b92736d8ca24dafc36a1e4247da0c9ac5925 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.kt @@ -122,21 +122,25 @@ class TvConversationFragment : BaseSupportFragment<ConversationPresenter, Conver private fun displaySpeechRecognizer() { if (!checkAudioPermission()) return try { - val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) - .putExtra( - RecognizerIntent.EXTRA_LANGUAGE_MODEL, - RecognizerIntent.LANGUAGE_MODEL_FREE_FORM - ) - .putExtra( - RecognizerIntent.EXTRA_PROMPT, - getText(R.string.conversation_input_speech_hint) - ) - startActivityForResult(intent, REQUEST_SPEECH_CODE) + startActivityForResult(Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) + .putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM) + .putExtra(RecognizerIntent.EXTRA_PROMPT, getText(R.string.conversation_input_speech_hint)), REQUEST_SPEECH_CODE) } catch (e: Exception) { Snackbar.make(requireView(), "Can't get voice input", Snackbar.LENGTH_SHORT).show() } } + override fun displayErrorToast(error: Error) { + val errorString: String = when (error) { + Error.NO_INPUT -> getString(R.string.call_error_no_camera_no_microphone) + Error.INVALID_FILE -> getString(R.string.invalid_file) + Error.NOT_ABLE_TO_WRITE_FILE -> getString(R.string.not_able_to_write_file) + Error.NO_SPACE_LEFT -> getString(R.string.no_space_left_on_device) + else -> getString(R.string.generic_error) + } + Toast.makeText(requireContext(), errorString, Toast.LENGTH_LONG).show() + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -557,27 +561,28 @@ class TvConversationFragment : BaseSupportFragment<ConversationPresenter, Conver } override fun displayContact(conversation: Conversation) { - val contacts = conversation.contacts + val contacts = conversation.contact ?: return mCompositeDisposable.clear() mCompositeDisposable.add(AvatarFactory.getAvatar(requireContext(), conversation, true) - .doOnSuccess { d: Drawable? -> + .doOnSuccess { d: Drawable -> mConversationAvatar = d as AvatarDrawable? - mParticipantAvatars[contacts[0].primaryNumber] = AvatarDrawable(d!!) + mParticipantAvatars[contacts.primaryNumber] = AvatarDrawable(d) } - .flatMapObservable { d: Drawable? -> contacts[0].updatesSubject } + .flatMapObservable { d: Drawable -> contacts.updatesSubject } .observeOn(AndroidSchedulers.mainThread()) .subscribe { c: Contact -> val id = c.ringUsername val displayName = c.displayName if (binding != null) { binding!!.title.text = displayName - if (TextUtils.isEmpty(displayName) || displayName != id) binding!!.subtitle.text = - id else binding!!.subtitle.visibility = View.GONE + if (TextUtils.isEmpty(displayName) || displayName != id) + binding!!.subtitle.text = id + else + binding!!.subtitle.visibility = View.GONE } mConversationAvatar!!.update(c) - val uri = contacts[0].primaryNumber - val a = mParticipantAvatars[uri] - a?.update(c) + val uri = contacts.primaryNumber + mParticipantAvatars[uri]?.update(c) if (mAdapter != null) mAdapter!!.setPhoto() }) } @@ -658,38 +663,26 @@ class TvConversationFragment : BaseSupportFragment<ConversationPresenter, Conver } override fun openFilePicker() {} - override fun acceptFile( - accountId: String, - conversationUri: net.jami.model.Uri, - transfer: DataTransfer - ) { + override fun acceptFile(accountId: String, conversationUri: net.jami.model.Uri, transfer: DataTransfer) { val cacheDir = requireContext().cacheDir val spaceLeft = getSpaceLeft(cacheDir.toString()) if (spaceLeft == -1L || transfer.totalSize > spaceLeft) { presenter.noSpaceLeft() return } - requireActivity().startService( - Intent(DRingService.ACTION_FILE_ACCEPT) - .setClass(requireContext(), DRingService::class.java) - .setData(ConversationPath.toUri(accountId, conversationUri)) - .putExtra(DRingService.KEY_MESSAGE_ID, transfer.messageId) - .putExtra(DRingService.KEY_TRANSFER_ID, transfer.fileId) - ) - } - - override fun refuseFile( - accountId: String, - conversationUri: net.jami.model.Uri, - transfer: DataTransfer - ) { - requireActivity().startService( - Intent(DRingService.ACTION_FILE_CANCEL) - .setClass(requireContext(), DRingService::class.java) - .setData(ConversationPath.toUri(accountId, conversationUri)) - .putExtra(DRingService.KEY_MESSAGE_ID, transfer.messageId) - .putExtra(DRingService.KEY_TRANSFER_ID, transfer.fileId) - ) + requireActivity().startService(Intent(DRingService.ACTION_FILE_ACCEPT) + .setClass(requireContext(), DRingService::class.java) + .setData(ConversationPath.toUri(accountId, conversationUri)) + .putExtra(DRingService.KEY_MESSAGE_ID, transfer.messageId) + .putExtra(DRingService.KEY_TRANSFER_ID, transfer.fileId)) + } + + override fun refuseFile(accountId: String, conversationUri: net.jami.model.Uri, transfer: DataTransfer) { + requireActivity().startService(Intent(DRingService.ACTION_FILE_CANCEL) + .setClass(requireContext(), DRingService::class.java) + .setData(ConversationPath.toUri(accountId, conversationUri)) + .putExtra(DRingService.KEY_MESSAGE_ID, transfer.messageId) + .putExtra(DRingService.KEY_TRANSFER_ID, transfer.fileId)) } companion object { diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/BaseBrowseFragment.java b/ring-android/app/src/main/java/cx/ring/tv/main/BaseBrowseFragment.java index 55cb169742c7851f0bb76deac006f9c67d9ee0e7..e15f99e6b8743969bfea0692bceabf3a162b9113 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/main/BaseBrowseFragment.java +++ b/ring-android/app/src/main/java/cx/ring/tv/main/BaseBrowseFragment.java @@ -24,16 +24,12 @@ import android.os.Bundle; import androidx.annotation.NonNull; import androidx.leanback.app.BrowseSupportFragment; import android.view.View; -import android.widget.Toast; import javax.inject.Inject; -import cx.ring.R; -import net.jami.model.Error; -import net.jami.mvp.BaseView; import net.jami.mvp.RootPresenter; -public class BaseBrowseFragment<T extends RootPresenter> extends BrowseSupportFragment implements BaseView { +public class BaseBrowseFragment<T extends RootPresenter> extends BrowseSupportFragment { protected static final String TAG = BaseBrowseFragment.class.getSimpleName(); @@ -54,20 +50,6 @@ public class BaseBrowseFragment<T extends RootPresenter> extends BrowseSupportFr presenter.unbindView(); } - public void displayErrorToast(Error error) { - String errorString; - switch (error) { - case NO_INPUT: - errorString = getString(R.string.call_error_no_camera_no_microphone); - break; - default: - errorString = getString(R.string.generic_error); - break; - } - - Toast.makeText(getActivity(), errorString, Toast.LENGTH_LONG).show(); - } - protected void initPresenter(T presenter) { } diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/BaseDetailFragment.java b/ring-android/app/src/main/java/cx/ring/tv/main/BaseDetailFragment.java index 42d9e45e5cf5dad7db7fe4e3b45a0eb8bb0be8c2..bff84a7f60e07ca1af17e78b6aca2b4edbd23e20 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/main/BaseDetailFragment.java +++ b/ring-android/app/src/main/java/cx/ring/tv/main/BaseDetailFragment.java @@ -25,13 +25,11 @@ import android.widget.Toast; import javax.inject.Inject; +import androidx.annotation.NonNull; import androidx.leanback.app.DetailsSupportFragment; -import cx.ring.R; -import net.jami.model.Error; -import net.jami.mvp.BaseView; import net.jami.mvp.RootPresenter; -public class BaseDetailFragment<T extends RootPresenter> extends DetailsSupportFragment implements BaseView { +public class BaseDetailFragment<T extends RootPresenter> extends DetailsSupportFragment { protected static final String TAG = BaseBrowseFragment.class.getSimpleName(); @@ -39,7 +37,7 @@ public class BaseDetailFragment<T extends RootPresenter> extends DetailsSupportF protected T presenter; @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); //Be sure to do the injection in onCreateView method @@ -53,20 +51,6 @@ public class BaseDetailFragment<T extends RootPresenter> extends DetailsSupportF presenter.unbindView(); } - public void displayErrorToast(Error error) { - String errorString; - switch (error) { - case NO_INPUT: - errorString = getString(R.string.call_error_no_camera_no_microphone); - break; - default: - errorString = getString(R.string.generic_error); - break; - } - - Toast.makeText(getActivity(), errorString, Toast.LENGTH_LONG).show(); - } - protected void initPresenter(T presenter) { } diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/HomeActivity.kt b/ring-android/app/src/main/java/cx/ring/tv/main/HomeActivity.kt index 31543355ab1e8f5cc24991afb565020eeb39b2d9..fa90219da5a0c814cf7e660cd309d4a04b3c4203 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/main/HomeActivity.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/main/HomeActivity.kt @@ -100,7 +100,7 @@ class HomeActivity : FragmentActivity() { out = Allocation.createTyped(rs, rgbaType, Allocation.USAGE_SCRIPT) blurIntrinsic = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs)).apply { setRadius(BLUR_RADIUS * size.width / 1080) - setInput(input) + setInput(out) } mBlurOutputBitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888) mBlurOut = Allocation.createFromBitmap(rs, mBlurOutputBitmap) @@ -119,7 +119,7 @@ class HomeActivity : FragmentActivity() { public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - JamiApplication.instance!!.startDaemon() + JamiApplication.instance?.startDaemon() setContentView(R.layout.tv_activity_home) mBackgroundManager = BackgroundManager.getInstance(this).apply { attach(window) } mPreviewView = findViewById(R.id.previewView) diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt index 44eceaf33343b7a4b74675711bf1a1645d7f60b3..3574f60303f1b1f21528d1bd9e6c8afebff7d8e6 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/main/MainFragment.kt @@ -35,7 +35,7 @@ import androidx.tvprovider.media.tv.ChannelLogoUtils import androidx.tvprovider.media.tv.PreviewProgram import androidx.tvprovider.media.tv.TvContractCompat import cx.ring.R -import cx.ring.services.VCardServiceImpl.Companion.loadProfile +import cx.ring.services.VCardServiceImpl import cx.ring.tv.account.TVAccountExport import cx.ring.tv.account.TVProfileEditingFragment import cx.ring.tv.account.TVShareActivity @@ -49,8 +49,8 @@ import cx.ring.tv.contact.TVContactFragment import cx.ring.tv.search.SearchActivity import cx.ring.tv.settings.TVSettingsActivity import cx.ring.tv.views.CustomTitleView -import cx.ring.utils.AndroidFileUtils.createImageFile -import cx.ring.utils.BitmapUtils.drawableToBitmap +import cx.ring.utils.AndroidFileUtils +import cx.ring.utils.BitmapUtils import cx.ring.utils.ContentUriHandler import cx.ring.utils.ConversationPath import cx.ring.views.AvatarDrawable @@ -67,15 +67,10 @@ import net.jami.smartlist.SmartListViewModel import net.jami.utils.QRCodeUtils import java.io.BufferedOutputStream import java.io.FileOutputStream -import java.util.* -import cx.ring.tv.cards.ShadowRowPresenterSelector - -import androidx.leanback.widget.ArrayObjectAdapter @AndroidEntryPoint class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { - //private TVContactFragment mContactFragment; - private var mSpinnerFragment: SpinnerFragment? = null + private val mSpinnerFragment: SpinnerFragment = SpinnerFragment() private var cardRowAdapter: ArrayObjectAdapter? = null private var contactRequestRowAdapter: ArrayObjectAdapter? = null private var mTitleView: CustomTitleView? = null @@ -85,6 +80,7 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { private var accountSettingsRow: ListRow? = null private val mDisposable = CompositeDisposable() private val mHomeChannelDisposable = CompositeDisposable() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) headersState = HEADERS_DISABLED @@ -93,7 +89,7 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { mTitleView = view.findViewById(R.id.browse_title_group) super.onViewCreated(view, savedInstanceState) - setupUIElements(requireActivity()) + setupUIElements(requireContext()) } override fun onDestroyView() { @@ -147,11 +143,10 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { override fun showLoading(show: Boolean) { if (show) { - mSpinnerFragment = SpinnerFragment() parentFragmentManager.beginTransaction() - .replace(R.id.main_browse_fragment, mSpinnerFragment!!).commitAllowingStateLoss() + .replace(R.id.main_browse_fragment, mSpinnerFragment).commitAllowingStateLoss() } else { - parentFragmentManager.beginTransaction().remove(mSpinnerFragment!!) + parentFragmentManager.beginTransaction().remove(mSpinnerFragment) .commitAllowingStateLoss() } } @@ -163,7 +158,7 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { } override fun showContacts(contacts: List<SmartListViewModel>) { - val cards: MutableList<Card?> = ArrayList(contacts.size + 1) + val cards: MutableList<Card> = ArrayList(contacts.size + 1) cards.add(IconCardHelper.getAddContactCard(requireContext())) for (contact in contacts) cards.add(ContactCard(contact)) cardRowAdapter!!.setItems(cards, null) @@ -199,7 +194,7 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { } .subscribeOn(Schedulers.io()) .subscribe({ program -> cr.insert(TvContractCompat.PreviewPrograms.CONTENT_URI, program.toContentValues()) } - ) { e: Throwable? -> Log.w(TAG, "Error updating home channel", e) }) + ) { e: Throwable -> Log.w(TAG, "Error updating home channel", e) }) } override fun showContactRequests(contacts: List<SmartListViewModel>) { @@ -233,17 +228,17 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { val context = requireContext() val address = account.displayUsername mDisposable.clear() - mDisposable.add(loadProfile(context, account) - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext { profile -> - val name = profile.first - if (name != null && name.isNotEmpty()) { - mTitleView?.setAlias(name) - title = address ?: "" - } else { - mTitleView?.setAlias(address) - } + mDisposable.add(VCardServiceImpl.loadProfile(context, account) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext { profile -> + val name = profile.displayName + if (name != null && name.isNotEmpty()) { + mTitleView?.setAlias(name) + title = address ?: "" + } else { + mTitleView?.setAlias(address) } + } .map { p -> AvatarDrawable.build(context, account, p, true) } .subscribe { a -> mTitleView?.apply { @@ -252,8 +247,8 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { logoView.setImageDrawable(a) } }) - qrCard!!.setDrawable(prepareAccountQr(context, account.uri)) - accountSettingsRow!!.adapter.notifyItemRangeChanged(QR_ITEM_POSITION, 1) + qrCard?.setDrawable(prepareAccountQr(context, account.uri)) + accountSettingsRow?.adapter!!.notifyItemRangeChanged(QR_ITEM_POSITION, 1) } override fun showExportDialog(pAccountID: String, hasPassword: Boolean) { @@ -296,16 +291,12 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { .commit() } else if (item is IconCard) { when (item.type) { - Card.Type.ACCOUNT_ADD_DEVICE -> presenter!!.onExportClicked() - Card.Type.ACCOUNT_EDIT_PROFILE -> presenter!!.onEditProfileClicked() + Card.Type.ACCOUNT_ADD_DEVICE -> presenter.onExportClicked() + Card.Type.ACCOUNT_EDIT_PROFILE -> presenter.onEditProfileClicked() Card.Type.ACCOUNT_SHARE_ACCOUNT -> { val view = (itemViewHolder.view as CardView).mainImageView val intent = Intent(activity, TVShareActivity::class.java) - val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation( - requireActivity(), - view, - TVShareActivity.SHARED_ELEMENT_NAME - ).toBundle() + val bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(requireActivity(), view, TVShareActivity.SHARED_ELEMENT_NAME).toBundle() requireActivity().startActivity(intent, bundle) } Card.Type.ADD_CONTACT -> startActivity(Intent(activity, SearchActivity::class.java)) @@ -317,7 +308,7 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { } companion object { - private val TAG = MainFragment::class.java.simpleName + private val TAG = MainFragment::class.simpleName!! // Sections headers ids private const val HEADER_CONTACTS: Long = 0 @@ -348,7 +339,7 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { val targetSize = (AvatarFactory.SIZE_NOTIF * context.resources.displayMetrics.density).toInt() val targetPaddingSize = (AvatarFactory.SIZE_PADDING * context.resources.displayMetrics.density).toInt() ChannelLogoUtils.storeChannelLogo( - context, channelId, drawableToBitmap(context.getDrawable(R.drawable.ic_jami_48)!!, targetSize, targetPaddingSize)) + context, channelId, BitmapUtils.drawableToBitmap(context.getDrawable(R.drawable.ic_jami_48)!!, targetSize, targetPaddingSize)) TvContractCompat.requestChannelBrowsable(context, channelId) } else { cr.update(TvContractCompat.buildChannelUri(channelId), channel.toContentValues(), null, null) @@ -356,19 +347,14 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { return channelId } - private fun buildProgram( - context: Context, - vm: SmartListViewModel, - launcherName: String?, - channelId: Long - ): Single<PreviewProgram> { + private fun buildProgram(context: Context, vm: SmartListViewModel, launcherName: String?, channelId: Long): Single<PreviewProgram> { return AvatarDrawable.Builder() .withViewModel(vm) .withPresence(false) .buildAsync(context) - .map { avatar: AvatarDrawable? -> - val file = createImageFile(context) - val bitmapAvatar = drawableToBitmap(avatar!!, 256) + .map { avatar: AvatarDrawable -> + val file = AndroidFileUtils.createImageFile(context) + val bitmapAvatar = BitmapUtils.drawableToBitmap(avatar, 256) BufferedOutputStream(FileOutputStream(file)).use { os -> bitmapAvatar.compress(Bitmap.CompressFormat.PNG, 100, os) } @@ -376,11 +362,8 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { val uri = FileProvider.getUriForFile(context, ContentUriHandler.AUTHORITY_FILES, file) // Grant permission to launcher - if (launcherName != null) context.grantUriPermission( - launcherName, - uri, - Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION - ) + if (launcherName != null) + context.grantUriPermission(launcherName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) val contactBuilder = PreviewProgram.Builder() .setChannelId(channelId) .setType(TvContractCompat.PreviewPrograms.TYPE_CLIP) @@ -388,15 +371,13 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { .setAuthor(vm.contacts[0].ringUsername) .setPosterArtAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_1_1) .setPosterArtUri(uri) - .setIntentUri( - Uri.Builder() - .scheme(ContentUriHandler.SCHEME_TV) - .authority(ContentUriHandler.AUTHORITY) - .appendPath(ContentUriHandler.PATH_TV_CONVERSATION) - .appendPath(vm.accountId) - .appendPath(vm.uri.uri) - .build() - ) + .setIntentUri(Uri.Builder() + .scheme(ContentUriHandler.SCHEME_TV) + .authority(ContentUriHandler.AUTHORITY) + .appendPath(ContentUriHandler.PATH_TV_CONVERSATION) + .appendPath(vm.accountId) + .appendPath(vm.uri.uri) + .build()) .setInternalProviderId(vm.uuid) contactBuilder.build() } @@ -406,7 +387,7 @@ class MainFragment : BaseBrowseFragment<MainPresenter>(), MainView { Log.w(TAG, "prepareAccountQr $accountId") if (accountId == null || accountId.isEmpty()) return null val pad = 16 - val qrCodeData = QRCodeUtils.encodeStringAsQRCodeData(accountId, 0X00000000, -0x1) + val qrCodeData = QRCodeUtils.encodeStringAsQRCodeData(accountId, 0X00000000, -0x1)!! val bitmap = Bitmap.createBitmap(qrCodeData.width + 2 * pad, qrCodeData.height + 2 * pad, Bitmap.Config.ARGB_8888) bitmap.setPixels( qrCodeData.data, diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.kt b/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.kt index 78fd9ff6d24624723271885d5a40a8309c3011f2..131d377effcd7a6e5aea318beec1fffc171b0c2b 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/main/MainPresenter.kt @@ -46,7 +46,7 @@ class MainPresenter @Inject constructor( } private fun loadConversations() { - view!!.showLoading(true) + view?.showLoading(true) mCompositeDisposable.add(mConversationFacade.getSmartList(true) .switchMap { viewModels: List<Observable<SmartListViewModel>> -> if (viewModels.isEmpty()) SmartListViewModel.EMPTY_RESULTS @@ -56,10 +56,10 @@ class MainPresenter @Inject constructor( .throttleLatest(150, TimeUnit.MILLISECONDS, mUiScheduler) .observeOn(mUiScheduler) .subscribe({ viewModels: List<SmartListViewModel> -> - val view = view - view!!.showLoading(false) + val view = view ?: return@subscribe + view.showLoading(false) view.showContacts(viewModels) - }) { e: Throwable? -> Log.w(TAG, "showConversations error ", e) }) + }) { e: Throwable -> Log.w(TAG, "showConversations error ", e) }) mCompositeDisposable.add(mConversationFacade.pendingList .switchMap { viewModels: List<Observable<SmartListViewModel>> -> if (viewModels.isEmpty()) SmartListViewModel.EMPTY_RESULTS @@ -73,10 +73,10 @@ class MainPresenter @Inject constructor( }) { e: Throwable -> Log.w(TAG, "showConversations error ", e) }) } - fun reloadAccountInfo() { - mCompositeDisposable.add(mAccountService.currentAccountSubject + private fun reloadAccountInfo() { + mCompositeDisposable.add(mAccountService.currentProfileAccountSubject .observeOn(mUiScheduler) - .subscribe({ account: Account -> view?.displayAccountInfo(HomeNavigationViewModel(account, null)) }) + .subscribe({ accountProfile -> view?.displayAccountInfo(HomeNavigationViewModel(accountProfile.first, accountProfile.second)) }) { e: Throwable -> Log.d(TAG, "reloadAccountInfos getProfileAccountList onError", e) }) mCompositeDisposable.add(mAccountService.observableAccounts @@ -90,11 +90,11 @@ class MainPresenter @Inject constructor( } fun onEditProfileClicked() { - view!!.showProfileEditing() + view?.showProfileEditing() } fun onShareAccountClicked() { - view!!.showAccountShare() + view?.showAccountShare() } fun onSettingsClicked() { diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java b/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java index be65e03e8b33b9ff59dac9620e3dd488c58be3af..34223adabbd8f627941b9499f515a53c3754d833 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java +++ b/ring-android/app/src/main/java/cx/ring/tv/main/MainView.java @@ -38,8 +38,6 @@ public interface MainView { void callContact(String accountID, String ringID); - void displayErrorToast(Error error); - void displayAccountInfo(HomeNavigationViewModel viewModel); void updateModel(Account account); diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/SpinnerFragment.java b/ring-android/app/src/main/java/cx/ring/tv/main/SpinnerFragment.java deleted file mode 100644 index f89b3805f505a5695b1956f01ebe2ecc8057e4d2..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/tv/main/SpinnerFragment.java +++ /dev/null @@ -1,53 +0,0 @@ -package cx.ring.tv.main; -/* - * Copyright (C) 2014 The Android Open Source Project - * - * 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. - */ - - -import android.os.Bundle; -import androidx.fragment.app.Fragment; -import android.view.Gravity; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ProgressBar; - -/** - * SpinnerFragment shows spinning progressbar to notify user that - * application is processing something (while downloading, or preparing sth etc.) - * <p> - * Example of usage in AsyncTask - * + Start showing: OnPreExecute - * mSpinnerFragment = new SpinnerFragment(); - * getFragmentManager().beginTransaction().add(R.id.some_view_group, mSpinnerFragment).commit(); - * + Stop showing: OnPostExecute - * getFragmentManager().beginTransaction().remove(mSpinnerFragment).commit(); - */ -public class SpinnerFragment extends Fragment { - - private static final int SPINNER_WIDTH = 100; - private static final int SPINNER_HEIGHT = 100; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - ProgressBar progressBar = new ProgressBar(container.getContext()); - if (container instanceof FrameLayout) { - FrameLayout.LayoutParams layoutParams = - new FrameLayout.LayoutParams(SPINNER_WIDTH, SPINNER_HEIGHT, Gravity.CENTER); - progressBar.setLayoutParams(layoutParams); - } - return progressBar; - } -} diff --git a/ring-android/app/src/main/java/cx/ring/tv/main/SpinnerFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/main/SpinnerFragment.kt new file mode 100644 index 0000000000000000000000000000000000000000..9a017c4a283717afc0d6c31d1f6d4d424d360c84 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/tv/main/SpinnerFragment.kt @@ -0,0 +1,26 @@ +package cx.ring.tv.main + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.os.Bundle +import android.widget.FrameLayout +import android.view.Gravity +import android.view.View +import android.widget.ProgressBar +import androidx.fragment.app.Fragment + +class SpinnerFragment : Fragment() { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val progressBar = ProgressBar(container!!.context) + if (container is FrameLayout) { + val layoutParams = FrameLayout.LayoutParams(SPINNER_WIDTH, SPINNER_HEIGHT, Gravity.CENTER) + progressBar.layoutParams = layoutParams + } + return progressBar + } + + companion object { + private const val SPINNER_WIDTH = 100 + private const val SPINNER_HEIGHT = 100 + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/BaseSearchFragment.java b/ring-android/app/src/main/java/cx/ring/tv/search/BaseSearchFragment.java index caa20fa1dd3a31458402357a3bacb2eb2666c2f7..0b43e227add28d950fbe94acbc468592459d4bf3 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/search/BaseSearchFragment.java +++ b/ring-android/app/src/main/java/cx/ring/tv/search/BaseSearchFragment.java @@ -21,27 +21,20 @@ package cx.ring.tv.search; import android.os.Bundle; import android.view.View; -import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.leanback.app.SearchSupportFragment; import javax.inject.Inject; -import cx.ring.R; -import net.jami.model.Error; -import net.jami.mvp.BaseView; import net.jami.mvp.RootPresenter; -public class BaseSearchFragment<T extends RootPresenter> extends SearchSupportFragment - implements BaseView { - - protected static final String TAG = BaseSearchFragment.class.getSimpleName(); - +public class BaseSearchFragment<T extends RootPresenter> extends SearchSupportFragment { @Inject protected T presenter; @Override - public void onViewCreated(View view, Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); //Be sure to do the injection in onCreateView method @@ -55,24 +48,6 @@ public class BaseSearchFragment<T extends RootPresenter> extends SearchSupportFr presenter.unbindView(); } - @Override - public void displayErrorToast(Error error) { - String errorString; - switch (error) { - case NO_MICROPHONE: - errorString = getString(R.string.call_error_no_microphone); - break; - case NO_INPUT: - errorString = getString(R.string.call_error_no_camera_no_microphone); - break; - default: - errorString = getString(R.string.generic_error); - break; - } - - Toast.makeText(getActivity(), errorString, Toast.LENGTH_LONG).show(); - } - protected void initPresenter(T presenter) { } diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchFragment.kt b/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchFragment.kt index fe5843efc1ba9871304cdf3423150b3bf0335eae..0dce916bdc3039e5d5b7f15bed7f439e44e96b06 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchFragment.kt +++ b/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchFragment.kt @@ -52,7 +52,7 @@ class ContactSearchFragment : BaseSearchFragment<ContactSearchPresenter>(), super.onCreate(savedInstanceState) setSearchResultProvider(this) setOnItemViewClickedListener { _, item: Any, _, _ -> - presenter.contactClicked((item as ContactCard).model) + presenter.contactClicked((item as ContactCard).model!!) } badgeDrawable = ContextCompat.getDrawable(requireContext(), R.mipmap.ic_launcher) setSearchQuery("", false) diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchPresenter.java b/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchPresenter.java deleted file mode 100644 index 3e875738df64fd7cd1a13e23e8677ba3152d387a..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchPresenter.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.tv.search; - -import java.util.concurrent.TimeUnit; - -import javax.inject.Inject; -import javax.inject.Named; - -import net.jami.model.Account; -import net.jami.model.Contact; -import net.jami.model.Uri; -import net.jami.mvp.RootPresenter; -import net.jami.services.AccountService; -import net.jami.services.HardwareService; -import net.jami.services.VCardService; -import net.jami.smartlist.SmartListViewModel; - -import io.reactivex.rxjava3.core.Scheduler; -import io.reactivex.rxjava3.subjects.PublishSubject; - -public class ContactSearchPresenter extends RootPresenter<ContactSearchView> { - - private final AccountService mAccountService; - - private Contact mContact; - @Inject - @Named("UiScheduler") - protected Scheduler mUiScheduler; - - private final PublishSubject<String> contactQuery = PublishSubject.create(); - - @Inject - public ContactSearchPresenter(AccountService accountService) { - mAccountService = accountService; - } - - @Override - public void bindView(ContactSearchView view) { - super.bindView(view); - mCompositeDisposable.add(contactQuery - .debounce(350, TimeUnit.MILLISECONDS) - .switchMapSingle(q -> mAccountService.findRegistrationByName(mAccountService.getCurrentAccount().getAccountID(), "", q)) - .observeOn(mUiScheduler) - .subscribe(q -> parseEventState(mAccountService.getAccount(q.getAccountId()), q.getName(), q.getAddress(), q.getState()))); - } - - @Override - public void unbindView() { - super.unbindView(); - } - - public void queryTextChanged(String query) { - if (query.equals("")) { - getView().clearSearch(); - } else { - Account currentAccount = mAccountService.getCurrentAccount(); - if (currentAccount == null) { - return; - } - - Uri uri = Uri.fromString(query); - if (uri.isHexId()) { - mContact = currentAccount.getContactFromCache(uri); - getView().displayContact(currentAccount.getAccountID(), mContact); - } else { - getView().clearSearch(); - contactQuery.onNext(query); - } - } - } - - private void parseEventState(Account account, String name, String address, int state) { - switch (state) { - case 0: - // on found - mContact = account.getContactFromCache(address); - mContact.setUsername(name); - getView().displayContact(account.getAccountID(), mContact); - break; - case 1: - // invalid name - Uri uriName = Uri.fromString(name); - if (uriName.isHexId()) { - mContact = account.getContactFromCache(uriName); - getView().displayContact(account.getAccountID(), mContact); - } else { - getView().clearSearch(); - } - break; - default: - // on error - Uri uriAddress = Uri.fromString(address); - if (uriAddress.isHexId()) { - mContact = account.getContactFromCache(uriAddress); - getView().displayContact(account.getAccountID(), mContact); - } else { - getView().clearSearch(); - } - break; - } - } - - public void contactClicked(SmartListViewModel model) { - getView().displayContactDetails(model); - } -} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchPresenter.kt b/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..e9b05edd1d4d35432df43ef80fbc1fccacc4d4b1 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchPresenter.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.tv.search + +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.subjects.PublishSubject +import net.jami.model.Account +import net.jami.model.Contact +import net.jami.model.Uri.Companion.fromString +import net.jami.mvp.RootPresenter +import net.jami.services.AccountService +import net.jami.services.AccountService.RegisteredName +import net.jami.smartlist.SmartListViewModel +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Named + +class ContactSearchPresenter @Inject constructor( + private val mAccountService: AccountService, + @Named("UiScheduler") var mUiScheduler: Scheduler +) : RootPresenter<ContactSearchView>() { + private var mContact: Contact? = null + private val contactQuery = PublishSubject.create<String>() + + override fun bindView(view: ContactSearchView) { + super.bindView(view) + mCompositeDisposable.add(contactQuery + .debounce(350, TimeUnit.MILLISECONDS) + .switchMapSingle { q: String -> + mAccountService.findRegistrationByName(mAccountService.currentAccount!!.accountID, "", q) + } + .observeOn(mUiScheduler) + .subscribe { q: RegisteredName -> parseEventState(mAccountService.getAccount(q.accountId)!!, q.name, q.address, q.state) }) + } + + fun queryTextChanged(query: String) { + if (query == "") { + view?.clearSearch() + } else { + val currentAccount = mAccountService.currentAccount ?: return + val uri = fromString(query) + if (uri.isHexId) { + mContact = currentAccount.getContactFromCache(uri) + view!!.displayContact(currentAccount.accountID, mContact) + } else { + view!!.clearSearch() + contactQuery.onNext(query) + } + } + } + + private fun parseEventState(account: Account, name: String, address: String?, state: Int) { + when (state) { + 0 -> { + // on found + mContact = account.getContactFromCache(address!!).apply { setUsername(name) } + view?.displayContact(account.accountID, mContact) + } + 1 -> { + // invalid name + val uriName = fromString(name) + if (uriName.isHexId) { + mContact = account.getContactFromCache(uriName) + view?.displayContact(account.accountID, mContact) + } else { + view?.clearSearch() + } + } + else -> { + // on error + val uriAddress = fromString(address!!) + if (uriAddress.isHexId) { + mContact = account.getContactFromCache(uriAddress) + view?.displayContact(account.accountID, mContact) + } else { + view?.clearSearch() + } + } + } + } + + fun contactClicked(model: SmartListViewModel) { + view?.displayContactDetails(model) + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchView.java b/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchView.java index e011340a1ce90e9c30848fe3ad7c36a518a19c30..14bc3b85c16285d296376c7a4003e8c379290793 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchView.java +++ b/ring-android/app/src/main/java/cx/ring/tv/search/ContactSearchView.java @@ -29,8 +29,6 @@ public interface ContactSearchView { void clearSearch(); - void displayErrorToast(Error error); - void startCall(String accountID, String number); void displayContactDetails(SmartListViewModel model); diff --git a/ring-android/app/src/main/java/cx/ring/tv/settings/TVSettingsFragment.java b/ring-android/app/src/main/java/cx/ring/tv/settings/TVSettingsFragment.java index 365daf3f59ff744fb2e767324646a09d9a097a49..90dbb1ab548089e81097f76bef4aa35f24baa3be 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/settings/TVSettingsFragment.java +++ b/ring-android/app/src/main/java/cx/ring/tv/settings/TVSettingsFragment.java @@ -39,9 +39,8 @@ import java.util.ArrayList; import java.util.Arrays; import cx.ring.R; -import cx.ring.application.JamiApplication; -import cx.ring.fragments.GeneralAccountPresenter; -import cx.ring.fragments.GeneralAccountView; +import net.jami.settings.GeneralAccountPresenter; +import net.jami.settings.GeneralAccountView; import net.jami.model.Account; import net.jami.model.ConfigKey; import net.jami.utils.Tuple; @@ -100,7 +99,7 @@ public class TVSettingsFragment extends LeanbackSettingsFragmentCompat { } @Override - public void addJamiPreferences(String accountId) { + public void addJamiPreferences(@NonNull String accountId) { } diff --git a/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.kt b/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.kt index ff6f2339bdabd6e59e56e476071d0f428205a9c5..f91e410563580b5edd411fc1e5bf27689a8c41ca 100644 --- a/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.kt +++ b/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.kt @@ -220,7 +220,7 @@ object AndroidFileUtils { getMimeType(uri.toString()) } - fun getMimeType(filename: String): String? { + fun getMimeType(filename: String): String { val pos = filename.lastIndexOf(".") var fileExtension: String? = null if (pos >= 0) { @@ -522,8 +522,8 @@ object AndroidFileUtils { private fun getOrientation(context: Context, photoUri: Uri): Int { val resolver = context.contentResolver ?: return 0 try { - resolver.query(photoUri, arrayOf(MediaStore.Images.ImageColumns.ORIENTATION), null, null, null).use { cursor -> - cursor!!.moveToFirst() + resolver.query(photoUri, arrayOf(MediaStore.Images.ImageColumns.ORIENTATION), null, null, null)!!.use { cursor -> + cursor.moveToFirst() return cursor.getInt(0) } } catch (e: Exception) { @@ -539,8 +539,8 @@ object AndroidFileUtils { private fun getExifOrientation(resolver: ContentResolver, photoUri: Uri): Int { if (Build.VERSION.SDK_INT > 23) { try { - resolver.openInputStream(photoUri).use { input -> - return ExifInterface(input!!) + resolver.openInputStream(photoUri)!!.use { input -> + return ExifInterface(input) .getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL) } } catch (e: Exception) { @@ -556,14 +556,11 @@ object AndroidFileUtils { } } - @JvmStatic fun isImage(s: String): Boolean { - return getMimeType(s)?.startsWith("image") ?: false + return getMimeType(s).startsWith("image") } - @JvmStatic fun getFileName(s: String): String { - val parts = s.split(Regex("/")).toTypedArray() - return parts[parts.size - 1] + return s.split('/').last() } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.kt b/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.kt index 3e6d99dbdf4e6bf407f867df58ca4b0982793b43..08cc927a90294b74e5017bb1370eecfa4842d5cd 100644 --- a/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.kt +++ b/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.kt @@ -67,7 +67,6 @@ object ContentUriHandler { return getUriForFile(context, authority, file, null) } - @JvmStatic fun getUriForFile(context: Context, authority: String, file: File, displayName: String?): Uri { return try { if (displayName == null) diff --git a/ring-android/app/src/main/java/cx/ring/utils/ConversationPath.kt b/ring-android/app/src/main/java/cx/ring/utils/ConversationPath.kt index fbd26a1dc45dd54f4e8463d4249a437c6d025032..24619fcbd1fbafd611ca12ba5579b9fddb1cccee 100644 --- a/ring-android/app/src/main/java/cx/ring/utils/ConversationPath.kt +++ b/ring-android/app/src/main/java/cx/ring/utils/ConversationPath.kt @@ -27,7 +27,6 @@ import cx.ring.BuildConfig import net.jami.model.Conversation import net.jami.model.Uri import net.jami.utils.StringUtils -import net.jami.utils.Tuple import java.util.* class ConversationPath { @@ -46,11 +45,6 @@ class ConversationPath { conversationId = conversationUri.uri } - constructor(path: Tuple<String, String>) { - accountId = path.first - conversationId = path.second - } - constructor(conversation: Conversation) { accountId = conversation.accountId conversationId = conversation.uri.uri @@ -117,7 +111,7 @@ class ConversationPath { return if (interaction.conversation is Conversation) toUri(interaction.account, (interaction.conversation as Conversation).uri) else - toUri(interaction.account, Uri.fromString(interaction.conversation!!.participant)) + toUri(interaction.account, Uri.fromString(interaction.conversation!!.participant!!)) } fun toBundle(accountId: String, uri: String): Bundle { diff --git a/ring-android/app/src/main/java/cx/ring/utils/NetworkUtils.java b/ring-android/app/src/main/java/cx/ring/utils/NetworkUtils.java deleted file mode 100644 index 574e104e2805c6f746a8897e26d4e932dd3917db..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/utils/NetworkUtils.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien Desousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.utils; - -import android.content.Context; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkInfo; - -public final class NetworkUtils { - /** - * Get the network info - */ - public static NetworkInfo getNetworkInfo(Context context) { - if (context == null) - return null; - ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - if (cm == null) - return null; - for (Network n: cm.getAllNetworks()) { - NetworkCapabilities caps = cm.getNetworkCapabilities(n); - if (caps != null && !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) - continue; - NetworkInfo nInfo = cm.getNetworkInfo(n); - if(nInfo != null && nInfo.isConnected()) - return nInfo; - } - return null; - } - - public static boolean isConnectivityAllowed(Context context) { - NetworkInfo info = NetworkUtils.getNetworkInfo(context); - return info != null && info.isConnected(); - } - public static boolean isPushAllowed(Context context, boolean allowMobile) { - if (allowMobile) - return true; - NetworkInfo info = NetworkUtils.getNetworkInfo(context); - return info != null && info.getType() != ConnectivityManager.TYPE_MOBILE; - } -} diff --git a/ring-android/app/src/main/java/cx/ring/utils/NetworkUtils.kt b/ring-android/app/src/main/java/cx/ring/utils/NetworkUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..ac49d93d22f9553b223552cffefc20cb2a5d2ca5 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/utils/NetworkUtils.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien Desousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.utils + +import android.content.Context +import android.net.NetworkInfo +import android.net.ConnectivityManager +import android.net.NetworkCapabilities + +object NetworkUtils { + /** + * Get the network info + */ + fun getNetworkInfo(context: Context): NetworkInfo? { + val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + for (n in cm.allNetworks) { + val caps = cm.getNetworkCapabilities(n) + if (caps != null && !caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) continue + val nInfo = cm.getNetworkInfo(n) + if (nInfo != null && nInfo.isConnected) + return nInfo + } + return null + } + + fun isConnectivityAllowed(context: Context): Boolean { + val info = getNetworkInfo(context) + return info != null && info.isConnected + } + + fun isPushAllowed(context: Context, allowMobile: Boolean): Boolean { + if (allowMobile) return true + val info = getNetworkInfo(context) + return info != null && info.type != ConnectivityManager.TYPE_MOBILE + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.kt b/ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.kt index e99b90922eb33164e75bff6c216a2bf791e9f5d6..1f78f9e105df83a625869c7a324996c15e129d68 100644 --- a/ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.kt +++ b/ring-android/app/src/main/java/cx/ring/viewholders/SmartListViewHolder.kt @@ -143,7 +143,7 @@ class SmartListViewHolder : RecyclerView.ViewHolder { } interface SmartListListeners { - fun onItemClick(smartListViewModel: SmartListViewModel) - fun onItemLongClick(smartListViewModel: SmartListViewModel) + fun onItemClick(item: SmartListViewModel) + fun onItemLongClick(item: SmartListViewModel) } } \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt b/ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt index 2e143fc6932379af7c7b9c5c35d190464c79d678..5984ba9264b08b984459d580430f40f5a90792aa 100644 --- a/ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt +++ b/ring-android/app/src/main/java/cx/ring/views/AvatarDrawable.kt @@ -34,9 +34,9 @@ import io.reactivex.rxjava3.core.Single import net.jami.model.Account import net.jami.model.Contact import net.jami.model.Conversation +import net.jami.model.Profile import net.jami.smartlist.SmartListViewModel import net.jami.utils.HashUtils -import net.jami.utils.Tuple import java.util.* class AvatarDrawable : Drawable { @@ -92,10 +92,10 @@ class AvatarDrawable : Drawable { return VCardServiceImpl.loadProfile(context, account) .map { profile -> build(context, account, profile, crop) } } - fun build(context: Context, account: Account, profile: Tuple<String?, Any?>, crop: Boolean): AvatarDrawable { + fun build(context: Context, account: Account, profile: Profile, crop: Boolean = true): AvatarDrawable { return Builder() - .withPhoto(profile.second as Bitmap?) - .withNameData(profile.first, account.registeredName) + .withPhoto(profile.avatar as Bitmap?) + .withNameData(profile.displayName, account.registeredName) .withId(account.uri) .withCircleCrop(crop) .build(context) @@ -294,7 +294,9 @@ class AvatarDrawable : Drawable { avatarText = convertNameToAvatarText( if (TextUtils.isEmpty(profileName)) username else profileName ) - bitmaps?.set(0, contact.photo as Bitmap) + contact.photo?.let { photo -> + bitmaps?.set(0, photo as Bitmap) + } isOnline = contact.isOnline update = true } @@ -618,14 +620,7 @@ class AvatarDrawable : Drawable { val bitmap = bitmaps[i] val subBounds = getSubBounds(realBounds, bitmaps.size, i) if (subBounds != null) { - fit( - bitmap.width, - bitmap.height, - subBounds.width(), - subBounds.height(), - false, - inBounds!![i] - ) + fit(bitmap.width, bitmap.height, subBounds.width(), subBounds.height(), false, inBounds!![i]) backgroundBounds!![i].set(subBounds) } } diff --git a/ring-android/app/src/main/java/cx/ring/views/SwitchButton.java b/ring-android/app/src/main/java/cx/ring/views/SwitchButton.java deleted file mode 100644 index a5f238032d9bb5f32be83d618ab1587820011942..0000000000000000000000000000000000000000 --- a/ring-android/app/src/main/java/cx/ring/views/SwitchButton.java +++ /dev/null @@ -1,529 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * 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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package cx.ring.views; - -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.PointF; -import android.graphics.RectF; -import android.graphics.drawable.LayerDrawable; -import android.graphics.drawable.RotateDrawable; -import android.os.Build; -import android.text.Layout; -import android.text.StaticLayout; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.TypedValue; -import android.view.MotionEvent; -import android.view.SoundEffectConstants; -import android.view.ViewConfiguration; -import android.view.ViewParent; -import android.view.animation.AccelerateDecelerateInterpolator; -import android.widget.CompoundButton; - -import androidx.annotation.Nullable; -import androidx.core.content.res.ResourcesCompat; - -import cx.ring.R; - -public class SwitchButton extends CompoundButton { - public static final int DEFAULT_THUMB_SIZE_DP = 20; - public static final int DEFAULT_THUMB_MARGIN_DP = 2; - public static final int DEFAULT_ANIMATION_DURATION = 250; - public static final int DEFAULT_SWITCH_WIDTH = 72; - - private int mBackColor; - private final int mThumbSize; - private int mBackWidth; - private int mBackHeight; - private final int mTouchSlop; - private final int mClickTimeout; - private float mThumbRadius, mBackRadius; - private float mTextWidth; - private float mTextHeight; - private float mProgress; - private float mStartX, mStartY, mLastX; - private boolean mReady = false; - private boolean mCatch = false; - private boolean mShowImage = false; - - private final PointF mThumbPos = new PointF(), mPresentThumbPos = new PointF(); - private final RectF mBackRectF = new RectF(), - mSafeRectF = new RectF(), - mTextOnRectF = new RectF(), - mTextOffRectF = new RectF(), - mThumbMargin = new RectF(); - private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final ValueAnimator mProgressAnimator = ValueAnimator.ofFloat(0, 0); - private CharSequence mStatus; - private Layout mOnLayout; - private Layout mOffLayout; - private final RotateDrawable mImageDrawable; - - private boolean mChangingState = false; - private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener = null; - - public SwitchButton(Context context) { - this(context, null); - } - - public SwitchButton(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SwitchButton(Context context, @Nullable AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SwitchButton); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - saveAttributeDataForStyleable(context, R.styleable.SwitchButton, attrs, ta, defStyle, 0); - } - - int backColor = ta.getColor(R.styleable.SwitchButton_backColor, getResources().getColor(R.color.grey_400)); - String status = ta.getString(R.styleable.SwitchButton_status); - ta.recycle(); - - mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); - mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); - - mProgressAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); - mProgressAnimator.addUpdateListener(valueAnimator -> setProgress((float) valueAnimator.getAnimatedValue())); - mProgressAnimator.setDuration(DEFAULT_ANIMATION_DURATION); - - LayerDrawable layerDrawable = (LayerDrawable) ResourcesCompat.getDrawable(getResources(), R.drawable.rotate, context.getTheme()); - mImageDrawable = (RotateDrawable) layerDrawable.findDrawableByLayerId(R.id.progress); - - setFocusable(true); - setClickable(true); - - mStatus = status; - mBackColor = backColor; - - float margin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_THUMB_MARGIN_DP, getResources().getDisplayMetrics()); - mThumbMargin.set(margin, margin, margin, margin); - mThumbSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_THUMB_SIZE_DP, getResources().getDisplayMetrics()); - - // size & measure params must larger than 1 - //float thumbRangeRatio = DEFAULT_THUMB_RANGE_RATIO; - //mThumbRangeRatio = mThumbMargin.width() >= 0 ? Math.max(thumbRangeRatio, 1) : thumbRangeRatio; - - // sync checked status - setProgress(isChecked() ? 1.f : 0.f); - - super.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (mChangingState) - return; - if (mChildOnCheckedChangeListener != null) - mChildOnCheckedChangeListener.onCheckedChanged(buttonView, isChecked); - }); - } - - private Layout makeLayout(CharSequence text) { - return new StaticLayout(text, getPaint(), (int) Math.ceil(Layout.getDesiredWidth(text, getPaint())), Layout.Alignment.ALIGN_CENTER, 1.f, 0, false); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (mOnLayout == null && !TextUtils.isEmpty(mStatus)) { - mOnLayout = makeLayout(mStatus); - } - if (mOffLayout == null && !TextUtils.isEmpty(mStatus)) { - mOffLayout = makeLayout(mStatus); - } - - float defaultWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_SWITCH_WIDTH, getResources().getDisplayMetrics()); - //float onWidth = DEFAULT_SWITCH_WIDTH; -// mOnLayout != null ? mOnLayout.getWidth() : 0; - //float offWidth = DEFAULT_SWITCH_WIDTH; -// mOffLayout != null ? mOffLayout.getWidth() : 0; - /*if (onWidth != 0 || offWidth != 0) { - mTextWidth = Math.max(onWidth, offWidth); - } else { - mTextWidth = 0; - }*/ - mTextWidth = defaultWidth; - - float onHeight = mOnLayout != null ? mOnLayout.getHeight() : 0; - float offHeight = mOffLayout != null ? mOffLayout.getHeight() : 0; - if (onHeight != 0 || offHeight != 0) { - mTextHeight = Math.max(onHeight, offHeight); - } else { - mTextHeight = 0; - } - - setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); - } - - /** - * SwitchButton use this formula to determine the final size of thumb, background and itself. - * <p> - * textWidth = max(onWidth, offWidth) - * thumbRange = thumbWidth * rangeRatio - * textExtraSpace = textWidth + textExtra - (moveRange - thumbWidth + max(thumbMargin.left, thumbMargin.right) + textThumbInset) - * backWidth = thumbRange + thumbMargin.left + thumbMargin.right + max(textExtraSpace, 0) - * contentSize = thumbRange + max(thumbMargin.left, 0) + max(thumbMargin.right, 0) + max(textExtraSpace, 0) - * - * @param widthMeasureSpec widthMeasureSpec - * @return measuredWidth - */ - private int measureWidth(int widthMeasureSpec) { - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int measuredWidth; - - if (widthMode == MeasureSpec.EXACTLY) { - measuredWidth = widthSize; - mBackWidth = widthSize - getPaddingLeft() - getPaddingRight(); - } else { - /* - If parent view want SwitchButton to determine it's size itself, we calculate the minimal - size of it's content. Further more, we ignore the limitation of widthSize since we want - to display SwitchButton in its actual size rather than compress the shape. - */ - mBackWidth = Math.max(ceil(mThumbSize + mThumbMargin.left + mThumbMargin.right + mTextWidth), 0); - measuredWidth = mBackWidth + getPaddingLeft() + getPaddingRight(); - } - return measuredWidth; - } - - private int measureHeight(int heightMeasureSpec) { - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int measuredHeight = heightSize; - - int contentSize; - int textExtraSpace; - if (heightMode == MeasureSpec.EXACTLY) { - //if (mThumbSize != 0) { - /* - If thumbHeight has been set, we calculate backHeight and check if there is enough room. - */ - mBackHeight = ceil(mThumbSize + mThumbMargin.top + mThumbMargin.bottom); - mBackHeight = ceil(Math.max(mBackHeight, mTextHeight)); - /*if (mBackHeight + getPaddingTop() + getPaddingBottom() - Math.min(0, mThumbMargin.top) - Math.min(0, mThumbMargin.bottom) > heightSize) { - // No enough room, we set thumbHeight to zero to calculate these value again. - mThumbHeight = 0; - }*/ - //} - - /*if (mThumbHeight == 0) { - mBackHeight = ceil(heightSize - getPaddingTop() - getPaddingBottom() + Math.min(0, mThumbMargin.top) + Math.min(0, mThumbMargin.bottom)); - if (mBackHeight < 0) { - mBackHeight = 0; - mThumbHeight = 0; - return measuredHeight; - } - mThumbHeight = ceil(mBackHeight - mThumbMargin.top - mThumbMargin.bottom); - } - if (mThumbHeight < 0) { - mBackHeight = 0; - mThumbHeight = 0; - return measuredHeight; - }*/ - } else { - /*if (mThumbHeight == 0) { - mThumbHeight = ceil(getResources().getDisplayMetrics().density * DEFAULT_THUMB_SIZE_DP); - }*/ - mBackHeight = ceil(mThumbSize + mThumbMargin.top + mThumbMargin.bottom); - /*if (mBackHeight < 0) { - mBackHeight = 0; - //mThumbHeight = 0; - return measuredHeight; - }*/ - textExtraSpace = ceil(mTextHeight - mBackHeight); - if (textExtraSpace > 0) { - mBackHeight += textExtraSpace; - //mThumbHeight += textExtraSpace; - } - contentSize = Math.max(mThumbSize, mBackHeight); - - measuredHeight = Math.max(contentSize, contentSize + getPaddingTop() + getPaddingBottom()); - measuredHeight = Math.max(measuredHeight, getSuggestedMinimumHeight()); - } - - return measuredHeight; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (w != oldw || h != oldh) { - setup(); - } - } - - static private int ceil(double dimen) { - return (int) Math.ceil(dimen); - } - - private void setup() { - if (mBackWidth == 0 || mBackHeight == 0) { - return; - } - - mThumbRadius = mThumbSize / 2f; - mBackRadius = Math.min(mBackWidth, mBackHeight) / 2f; - - int contentWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); - int contentHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); - - // max range of drawing content, when thumbMargin is negative, drawing range is larger than backWidth - int drawingWidth = mBackWidth; - int drawingHeight = mBackHeight; - - float thumbTop; - if (contentHeight <= drawingHeight) { - thumbTop = getPaddingTop() + mThumbMargin.top; - } else { - // center vertical in content area - thumbTop = getPaddingTop() + mThumbMargin.top + (contentHeight - drawingHeight + 1) / 2f; - } - - float thumbLeft; - if (contentWidth <= mBackWidth) { - thumbLeft = getPaddingLeft() + mThumbMargin.left; - } else { - thumbLeft = getPaddingLeft() + mThumbMargin.left + (contentWidth - drawingWidth + 1) / 2f; - } - mThumbPos.set(thumbLeft + mThumbRadius, thumbTop + mThumbRadius); - - float backLeft = thumbLeft - mThumbMargin.left; - mBackRectF.set(backLeft, - thumbTop - mThumbMargin.top, - backLeft + mBackWidth, - thumbTop - mThumbMargin.top + mBackHeight); - - mSafeRectF.set(thumbLeft, 0, mBackRectF.right - mThumbMargin.right - mThumbSize, 0); - - float minBackRadius = Math.min(mBackRectF.width(), mBackRectF.height()) / 2.f; - mBackRadius = Math.min(minBackRadius, mBackRadius); - - if (mOnLayout != null) { - float onLeft = mBackRectF.left + (mBackRectF.width() - mThumbSize - mThumbMargin.right - mOnLayout.getWidth()) / 2f; - float onTop = mBackRectF.top + (mBackRectF.height() - mOnLayout.getHeight()) / 2; - mTextOnRectF.set(onLeft, onTop, onLeft + mOnLayout.getWidth(), onTop + mOnLayout.getHeight()); - } - - if (mOffLayout != null) { - float offLeft = mBackRectF.right - (mBackRectF.width() - mThumbSize - mThumbMargin.left - mOffLayout.getWidth()) / 2f - mOffLayout.getWidth(); - float offTop = mBackRectF.top + (mBackRectF.height() - mOffLayout.getHeight()) / 2; - mTextOffRectF.set(offLeft, offTop, offLeft + mOffLayout.getWidth(), offTop + mOffLayout.getHeight()); - } - - int dWidth = mImageDrawable.getIntrinsicWidth(); - int dHeight = mImageDrawable.getIntrinsicHeight(); - int dTop = ceil(mBackRectF.top + (mBackRectF.height() - dHeight) / 2); - mImageDrawable.setBounds((int) ((mBackWidth - mThumbSize) / 2 - dWidth / 2), dTop, (int) ((mBackWidth - mThumbSize) / 2 + dWidth / 2), dTop + dHeight); - - mReady = true; - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - if (!mReady) { - setup(); - } - if (!mReady) { - return; - } - - mPaint.setColor(mBackColor); - canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint); - - // thumb - mPresentThumbPos.set(mThumbPos); - mPresentThumbPos.offset(mProgress * mSafeRectF.width(), 0); - mPaint.setColor(Color.WHITE); - canvas.drawCircle(mPresentThumbPos.x, mPresentThumbPos.y, mThumbRadius, mPaint); - - // image - if (mShowImage) { - mImageDrawable.draw(canvas); - } else { - // text - Layout switchText = getProgress() > 0.5 ? mOnLayout : mOffLayout; - RectF textRectF = getProgress() > 0.5 ? mTextOnRectF : mTextOffRectF; - if (switchText != null && textRectF != null) { - float alpha = getProgress() >= 0.75 ? getProgress() * 4 - 3 : (getProgress() < 0.25 ? 1 - getProgress() * 4 : 0); - switchText.getPaint().setAlpha((int) (Color.alpha(getCurrentTextColor()) * alpha)); - canvas.save(); - canvas.translate(textRectF.left, textRectF.top); - switchText.draw(canvas); - canvas.restore(); - } - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (!isEnabled() || !isClickable() || !isFocusable() || !mReady) { - return false; - } - - int action = event.getAction(); - - float deltaX = event.getX() - mStartX; - float deltaY = event.getY() - mStartY; - - switch (action) { - case MotionEvent.ACTION_DOWN: - mStartX = event.getX(); - mStartY = event.getY(); - mLastX = mStartX; - setPressed(true); - break; - - case MotionEvent.ACTION_MOVE: - float x = event.getX(); - setProgress(getProgress() + (x - mLastX) / mSafeRectF.width()); - mLastX = x; - if (!mCatch && (Math.abs(deltaX) > mTouchSlop / 2f || Math.abs(deltaY) > mTouchSlop / 2f)) { - if (deltaY == 0 || Math.abs(deltaX) > Math.abs(deltaY)) { - catchView(); - } else if (Math.abs(deltaY) > Math.abs(deltaX)) { - return false; - } - } - break; - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - mCatch = false; - setPressed(false); - float time = event.getEventTime() - event.getDownTime(); - if (Math.abs(deltaX) < mTouchSlop && Math.abs(deltaY) < mTouchSlop && time < mClickTimeout) { - performClick(); - } else { - boolean nextStatus = getProgress() > 0.5f; - if (nextStatus != isChecked()) { - playSoundEffect(SoundEffectConstants.CLICK); - setChecked(nextStatus); - } else { - animateToState(nextStatus); - } - } - break; - - default: - break; - } - return true; - } - - private float getProgress() { - return mProgress; - } - - private void setProgress(final float progress) { - float tempProgress = progress; - if (tempProgress > 1) { - tempProgress = 1; - } else if (tempProgress < 0) { - tempProgress = 0; - } - mProgress = tempProgress; - invalidate(); - } - - @Override - public void setOnCheckedChangeListener(@Nullable OnCheckedChangeListener listener) { - mChildOnCheckedChangeListener = listener; - } - - public void animateToState(boolean checked) { - if (mProgressAnimator == null) { - return; - } - if (mProgressAnimator.isRunning()) { - mProgressAnimator.cancel(); - } - if (checked) { - mProgressAnimator.setFloatValues(mProgress, 1f); - } else { - mProgressAnimator.setFloatValues(mProgress, 0); - } - mProgressAnimator.start(); - } - - private void catchView() { - ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } - mCatch = true; - } - - @Override - public void setChecked(boolean checked) { - if (isChecked() == checked) { - return; - } - animateToState(checked); - super.setChecked(checked); - } - - public void setCheckedSilent(boolean checked) { - mChangingState = true; - super.setChecked(checked); - setProgress(checked ? 1 : 0); - mChangingState = false; - } - - public int getBackColor() { - return mBackColor; - } - - public void setBackColor(int backColor) { - mBackColor = backColor; - invalidate(); - } - - public void setStatus(CharSequence status) { - mStatus = status; - - mOnLayout = null; - mOffLayout = null; - - mReady = false; - requestLayout(); - invalidate(); - } - - public CharSequence getStatus() { - return mStatus; - } - - public void showImage(boolean show) { - mShowImage = show; - invalidate(); - } - - public void startImageAnimation() { - ObjectAnimator anim = ObjectAnimator.ofInt(mImageDrawable, "level", 0, 10000); - anim.setDuration(500); - anim.setRepeatCount(ValueAnimator.INFINITE); - anim.start(); - } - -} diff --git a/ring-android/app/src/main/java/cx/ring/views/SwitchButton.kt b/ring-android/app/src/main/java/cx/ring/views/SwitchButton.kt new file mode 100644 index 0000000000000000000000000000000000000000..0b3f562fc63e99beeb3b98a9f04c76a785227d11 --- /dev/null +++ b/ring-android/app/src/main/java/cx/ring/views/SwitchButton.kt @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package cx.ring.views + +import android.animation.ObjectAnimator +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.* +import android.graphics.drawable.LayerDrawable +import android.graphics.drawable.RotateDrawable +import android.os.Build +import android.text.Layout +import android.text.StaticLayout +import android.text.TextUtils +import android.util.AttributeSet +import android.util.TypedValue +import android.view.MotionEvent +import android.view.SoundEffectConstants +import android.view.ViewConfiguration +import android.view.animation.AccelerateDecelerateInterpolator +import android.widget.CompoundButton +import androidx.core.content.res.ResourcesCompat +import cx.ring.R +import kotlin.math.* + +class SwitchButton(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : CompoundButton(context, attrs, defStyle) { + private var mBackColor: Int + private val mThumbSize: Int + private var mBackWidth = 0 + private var mBackHeight = 0 + private val mTouchSlop: Int = ViewConfiguration.get(context).scaledTouchSlop + private val mClickTimeout: Int = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout() + private var mThumbRadius = 0f + private var mBackRadius = 0f + private var mTextWidth = 0f + private var mTextHeight = 0f + private var mProgress = 0f + private var mStartX = 0f + private var mStartY = 0f + private var mLastX = 0f + private var mReady = false + private var mCatch = false + private var mShowImage = false + private val mThumbPos = PointF() + private val mPresentThumbPos = PointF() + private val mBackRectF = RectF() + private val mSafeRectF = RectF() + private val mTextOnRectF = RectF() + private val mTextOffRectF = RectF() + private val mThumbMargin = RectF() + private val mPaint = Paint(Paint.ANTI_ALIAS_FLAG) + private val mProgressAnimator = ValueAnimator.ofFloat(0f, 0f).apply { + interpolator = AccelerateDecelerateInterpolator() + addUpdateListener { valueAnimator -> progress = valueAnimator.animatedValue as Float } + duration = DEFAULT_ANIMATION_DURATION.toLong() + } + private var mStatus: CharSequence? + private var mOnLayout: Layout? = null + private var mOffLayout: Layout? = null + private val mImageDrawable: RotateDrawable + private var mChangingState = false + private var mChildOnCheckedChangeListener: OnCheckedChangeListener? = null + + @JvmOverloads + constructor(context: Context, attrs: AttributeSet? = null) : this(context, attrs, 0) + + private fun makeLayout(text: CharSequence?): Layout { + return StaticLayout(text, paint, ceil(Layout.getDesiredWidth(text, paint)).toInt(), Layout.Alignment.ALIGN_CENTER, 1f, 0f, false) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + if (mOnLayout == null && !TextUtils.isEmpty(mStatus)) { + mOnLayout = makeLayout(mStatus) + } + if (mOffLayout == null && !TextUtils.isEmpty(mStatus)) { + mOffLayout = makeLayout(mStatus) + } + val defaultWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_SWITCH_WIDTH.toFloat(), resources.displayMetrics) + mTextWidth = defaultWidth + val onHeight = if (mOnLayout != null) mOnLayout!!.height.toFloat() else 0f + val offHeight = if (mOffLayout != null) mOffLayout!!.height.toFloat() else 0f + mTextHeight = if (onHeight != 0f || offHeight != 0f) max(onHeight, offHeight) else 0f + setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)) + } + + /** + * SwitchButton use this formula to determine the final size of thumb, background and itself. + * + * + * textWidth = max(onWidth, offWidth) + * thumbRange = thumbWidth * rangeRatio + * textExtraSpace = textWidth + textExtra - (moveRange - thumbWidth + max(thumbMargin.left, thumbMargin.right) + textThumbInset) + * backWidth = thumbRange + thumbMargin.left + thumbMargin.right + max(textExtraSpace, 0) + * contentSize = thumbRange + max(thumbMargin.left, 0) + max(thumbMargin.right, 0) + max(textExtraSpace, 0) + * + * @param widthMeasureSpec widthMeasureSpec + * @return measuredWidth + */ + private fun measureWidth(widthMeasureSpec: Int): Int { + val widthSize = MeasureSpec.getSize(widthMeasureSpec) + val widthMode = MeasureSpec.getMode(widthMeasureSpec) + val measuredWidth: Int + if (widthMode == MeasureSpec.EXACTLY) { + measuredWidth = widthSize + mBackWidth = widthSize - paddingLeft - paddingRight + } else { + /* + If parent view want SwitchButton to determine it's size itself, we calculate the minimal + size of it's content. Further more, we ignore the limitation of widthSize since we want + to display SwitchButton in its actual size rather than compress the shape. + */ + mBackWidth = max(ceil((mThumbSize + mThumbMargin.left + mThumbMargin.right + mTextWidth).toDouble()), 0) + measuredWidth = mBackWidth + paddingLeft + paddingRight + } + return measuredWidth + } + + private fun measureHeight(heightMeasureSpec: Int): Int { + val heightSize = MeasureSpec.getSize(heightMeasureSpec) + val heightMode = MeasureSpec.getMode(heightMeasureSpec) + var measuredHeight = heightSize + val contentSize: Int + val textExtraSpace: Int + if (heightMode == MeasureSpec.EXACTLY) { + mBackHeight = ceil((mThumbSize + mThumbMargin.top + mThumbMargin.bottom)).toInt() + mBackHeight = ceil(max(mBackHeight.toFloat(), mTextHeight)).toInt() + } else { + mBackHeight = ceil((mThumbSize + mThumbMargin.top + mThumbMargin.bottom).toDouble()) + textExtraSpace = ceil((mTextHeight - mBackHeight).toDouble()) + if (textExtraSpace > 0) { + mBackHeight += textExtraSpace + } + contentSize = max(mThumbSize, mBackHeight) + measuredHeight = max(contentSize, contentSize + paddingTop + paddingBottom) + measuredHeight = max(measuredHeight, suggestedMinimumHeight) + } + return measuredHeight + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + if (w != oldw || h != oldh) { + setup() + } + } + + private fun setup() { + if (mBackWidth == 0 || mBackHeight == 0) { + return + } + mThumbRadius = mThumbSize / 2f + mBackRadius = min(mBackWidth, mBackHeight) / 2f + val contentWidth = measuredWidth - paddingLeft - paddingRight + val contentHeight = measuredHeight - paddingTop - paddingBottom + + // max range of drawing content, when thumbMargin is negative, drawing range is larger than backWidth + val drawingWidth = mBackWidth + val drawingHeight = mBackHeight + val thumbTop: Float = if (contentHeight <= drawingHeight) { + paddingTop + mThumbMargin.top + } else { + // center vertical in content area + paddingTop + mThumbMargin.top + (contentHeight - drawingHeight + 1) / 2f + } + val thumbLeft: Float = if (contentWidth <= mBackWidth) { + paddingLeft + mThumbMargin.left + } else { + paddingLeft + mThumbMargin.left + (contentWidth - drawingWidth + 1) / 2f + } + mThumbPos.set(thumbLeft + mThumbRadius, thumbTop + mThumbRadius) + val backLeft = thumbLeft - mThumbMargin.left + mBackRectF.set(backLeft, thumbTop - mThumbMargin.top, backLeft + mBackWidth, thumbTop - mThumbMargin.top + mBackHeight) + mSafeRectF.set(thumbLeft, 0f, mBackRectF.right - mThumbMargin.right - mThumbSize, 0f) + val minBackRadius = min(mBackRectF.width(), mBackRectF.height()) / 2f + mBackRadius = min(minBackRadius, mBackRadius) + mOnLayout?.let { onLayout -> + val onLeft = mBackRectF.left + (mBackRectF.width() - mThumbSize - mThumbMargin.right - onLayout.width) / 2f + val onTop = mBackRectF.top + (mBackRectF.height() - onLayout.height) / 2 + mTextOnRectF.set(onLeft, onTop, onLeft + onLayout.width, onTop + onLayout.height) + } + mOffLayout?.let { offLayout -> + val offLeft = mBackRectF.right - (mBackRectF.width() - mThumbSize - mThumbMargin.left - offLayout.width) / 2f - offLayout.width + val offTop = mBackRectF.top + (mBackRectF.height() - offLayout.height) / 2 + mTextOffRectF.set(offLeft, offTop, offLeft + offLayout.width, offTop + offLayout.height) + } + val dWidth = mImageDrawable.intrinsicWidth + val dHeight = mImageDrawable.intrinsicHeight + val dTop = ceil((mBackRectF.top + (mBackRectF.height() - dHeight) / 2).toDouble()) + mImageDrawable.setBounds( + ((mBackWidth - mThumbSize) / 2 - dWidth / 2), + dTop, + ((mBackWidth - mThumbSize) / 2 + dWidth / 2), + dTop + dHeight + ) + mReady = true + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + if (!mReady) { + setup() + } + if (!mReady) { + return + } + mPaint.color = mBackColor + canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint) + + // thumb + mPresentThumbPos.set(mThumbPos) + mPresentThumbPos.offset(mProgress * mSafeRectF.width(), 0f) + mPaint.color = Color.WHITE + canvas.drawCircle(mPresentThumbPos.x, mPresentThumbPos.y, mThumbRadius, mPaint) + + // image + if (mShowImage) { + mImageDrawable.draw(canvas) + } else { + // text + val switchText = if (progress > 0.5) mOnLayout else mOffLayout + val textRectF = if (progress > 0.5) mTextOnRectF else mTextOffRectF + if (switchText != null) { + val alpha: Float = + if (progress >= 0.75) progress * 4 - 3 else if (progress < 0.25) 1 - progress * 4 else 0f + switchText.paint.alpha = (Color.alpha(currentTextColor) * alpha).toInt() + canvas.save() + canvas.translate(textRectF.left, textRectF.top) + switchText.draw(canvas) + canvas.restore() + } + } + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + if (!isEnabled || !isClickable || !isFocusable || !mReady) { + return false + } + val action = event.action + val deltaX = event.x - mStartX + val deltaY = event.y - mStartY + when (action) { + MotionEvent.ACTION_DOWN -> { + mStartX = event.x + mStartY = event.y + mLastX = mStartX + isPressed = true + } + MotionEvent.ACTION_MOVE -> { + val x = event.x + progress += (x - mLastX) / mSafeRectF.width() + mLastX = x + if (!mCatch && (abs(deltaX) > mTouchSlop / 2f || abs(deltaY) > mTouchSlop / 2f)) { + if (deltaY == 0f || abs(deltaX) > abs(deltaY)) { + catchView() + } else if (abs(deltaY) > abs(deltaX)) { + return false + } + } + } + MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { + mCatch = false + isPressed = false + val time = (event.eventTime - event.downTime).toFloat() + if (abs(deltaX) < mTouchSlop && abs(deltaY) < mTouchSlop && time < mClickTimeout) { + performClick() + } else { + val nextStatus = progress > 0.5f + if (nextStatus != isChecked) { + playSoundEffect(SoundEffectConstants.CLICK) + isChecked = nextStatus + } else { + animateToState(nextStatus) + } + } + } + else -> { + } + } + return true + } + + private var progress: Float + get() = mProgress + set(progress) { + var tempProgress = progress + if (tempProgress > 1) { + tempProgress = 1f + } else if (tempProgress < 0) { + tempProgress = 0f + } + mProgress = tempProgress + invalidate() + } + + override fun setOnCheckedChangeListener(listener: OnCheckedChangeListener?) { + mChildOnCheckedChangeListener = listener + } + + private fun animateToState(checked: Boolean) { + if (mProgressAnimator.isRunning) { + mProgressAnimator.cancel() + } + mProgressAnimator.setFloatValues(mProgress, if (checked) 1f else 0f) + mProgressAnimator.start() + } + + private fun catchView() { + val parent = parent + parent?.requestDisallowInterceptTouchEvent(true) + mCatch = true + } + + override fun setChecked(checked: Boolean) { + if (isChecked == checked) { + return + } + animateToState(checked) + super.setChecked(checked) + } + + fun setCheckedSilent(checked: Boolean) { + mChangingState = true + super.setChecked(checked) + progress = if (checked) 1f else 0f + mChangingState = false + } + + var backColor: Int + get() = mBackColor + set(backColor) { + mBackColor = backColor + invalidate() + } + var status: CharSequence? + get() = mStatus + set(status) { + mStatus = status + mOnLayout = null + mOffLayout = null + mReady = false + requestLayout() + invalidate() + } + + fun showImage(show: Boolean) { + mShowImage = show + invalidate() + } + + fun startImageAnimation() { + val anim = ObjectAnimator.ofInt(mImageDrawable, "level", 0, 10000) + anim.duration = 500 + anim.repeatCount = ValueAnimator.INFINITE + anim.start() + } + + companion object { + const val DEFAULT_THUMB_SIZE_DP = 20 + const val DEFAULT_THUMB_MARGIN_DP = 2 + const val DEFAULT_ANIMATION_DURATION = 250 + const val DEFAULT_SWITCH_WIDTH = 72 + private fun ceil(dimen: Double): Int { + return kotlin.math.ceil(dimen).toInt() + } + } + + init { + val ta = context.obtainStyledAttributes(attrs, R.styleable.SwitchButton) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + saveAttributeDataForStyleable(context, R.styleable.SwitchButton, attrs, ta, defStyle, 0) + } + val backColor = ta.getColor(R.styleable.SwitchButton_backColor, resources.getColor(R.color.grey_400)) + val status = ta.getString(R.styleable.SwitchButton_status) + ta.recycle() + val layerDrawable = ResourcesCompat.getDrawable(resources, R.drawable.rotate, context.theme) as LayerDrawable? + mImageDrawable = layerDrawable!!.findDrawableByLayerId(R.id.progress) as RotateDrawable + isFocusable = true + isClickable = true + mStatus = status + mBackColor = backColor + val margin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_THUMB_MARGIN_DP.toFloat(), resources.displayMetrics) + mThumbMargin.set(margin, margin, margin, margin) + mThumbSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_THUMB_SIZE_DP.toFloat(), resources.displayMetrics).toInt() + // sync checked status + progress = if (isChecked) 1f else 0f + super.setOnCheckedChangeListener { buttonView: CompoundButton?, isChecked: Boolean -> + if (mChangingState) return@setOnCheckedChangeListener + mChildOnCheckedChangeListener?.onCheckedChanged(buttonView, isChecked) + } + } +} \ No newline at end of file diff --git a/ring-android/build.gradle b/ring-android/build.gradle index 7c4c426f85a8c34a7bddc03a5fa9d70796efd4de..86f96368d4b3bab71df13f1f451198246abe8e5d 100644 --- a/ring-android/build.gradle +++ b/ring-android/build.gradle @@ -4,10 +4,10 @@ buildscript { maven { url "https://maven.google.com" } mavenCentral() } - ext.kotlin_version = '1.5.21' + ext.kotlin_version = '1.5.30' ext.hilt_version = '2.38.1' dependencies { - classpath 'com.android.tools.build:gradle:7.0.0' + classpath 'com.android.tools.build:gradle:7.0.2' classpath 'com.google.gms:google-services:4.3.10' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" diff --git a/ring-android/libringclient/build.gradle b/ring-android/libringclient/build.gradle index 9b10c1954a54c67865ad6cf131ae36e4166a32e0..9807cb2b76ad6788d25d5a867b8bb7c4bffb652f 100644 --- a/ring-android/libringclient/build.gradle +++ b/ring-android/libringclient/build.gradle @@ -23,10 +23,10 @@ dependencies { testImplementation 'junit:junit:4.13.2' // RxJava - implementation 'io.reactivex.rxjava3:rxjava:3.0.13' + implementation 'io.reactivex.rxjava3:rxjava:3.1.1' // gson - implementation 'com.google.code.gson:gson:2.8.7' + implementation 'com.google.code.gson:gson:2.8.8' api "com.google.dagger:dagger:$hilt_version" kapt "com.google.dagger:dagger-compiler:$hilt_version" diff --git a/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardPresenter.java b/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardPresenter.java deleted file mode 100644 index dddac96d3c3fc720c5da9bfe02ee59198e0719bd..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardPresenter.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.account; - -import java.util.HashMap; -import java.util.Map; - -import javax.inject.Inject; -import javax.inject.Named; - -import net.jami.model.Account; -import net.jami.model.AccountConfig; -import net.jami.model.ConfigKey; -import net.jami.model.Settings; -import net.jami.mvp.AccountCreationModel; -import net.jami.mvp.RootPresenter; -import net.jami.services.AccountService; -import net.jami.services.DeviceRuntimeService; -import net.jami.services.PreferencesService; -import net.jami.utils.Log; -import net.jami.utils.StringUtils; - -import io.reactivex.rxjava3.core.Observable; -import io.reactivex.rxjava3.core.Scheduler; -import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.subjects.BehaviorSubject; - -public class AccountWizardPresenter extends RootPresenter<net.jami.account.AccountWizardView> { - - public static final String TAG = AccountWizardPresenter.class.getSimpleName(); - - private final AccountService mAccountService; - private final PreferencesService mPreferences; - private final DeviceRuntimeService mDeviceService; - private final Scheduler mUiScheduler; - - //private boolean mCreationError = false; - private boolean mCreatingAccount = false; - private String mAccountType; - private AccountCreationModel mAccountCreationModel; - - private Observable<Account> newAccount; - - @Inject - public AccountWizardPresenter(AccountService accountService, - PreferencesService preferences, - DeviceRuntimeService deviceService, - @Named("UiScheduler") Scheduler uiScheduler) { - mAccountService = accountService; - mPreferences = preferences; - mDeviceService = deviceService; - mUiScheduler = uiScheduler; - } - - public void init(String accountType) { - mAccountType = accountType; - if (AccountConfig.ACCOUNT_TYPE_SIP.equals(mAccountType)) { - getView().goToSipCreation(); - } else { - getView().goToHomeCreation(); - } - } - - private void setProxyDetails(net.jami.mvp.AccountCreationModel accountCreationModel, Map<String, String> details) { - if (accountCreationModel.isPush()) { - details.put(ConfigKey.PROXY_ENABLED.key(), AccountConfig.TRUE_STR); - String pushToken = mDeviceService.getPushToken(); - if (!StringUtils.isEmpty(pushToken)) - details.put(ConfigKey.PROXY_PUSH_TOKEN.key(), pushToken); - } - } - - public void initJamiAccountConnect(AccountCreationModel accountCreationModel, String defaultAccountName) { - Single<Map<String, String>> newAccount = initRingAccountDetails(defaultAccountName) - .map(accountDetails -> { - if (!StringUtils.isEmpty(accountCreationModel.getManagementServer())) { - accountDetails.put(ConfigKey.MANAGER_URI.key(), accountCreationModel.getManagementServer()); - if (!StringUtils.isEmpty(accountCreationModel.getUsername())) { - accountDetails.put(ConfigKey.MANAGER_USERNAME.key(), accountCreationModel.getUsername()); - } - } else if (!StringUtils.isEmpty(accountCreationModel.getUsername())) { - accountDetails.put(ConfigKey.ACCOUNT_USERNAME.key(), accountCreationModel.getUsername()); - } - if (!StringUtils.isEmpty(accountCreationModel.getPassword())) { - accountDetails.put(ConfigKey.ARCHIVE_PASSWORD.key(), accountCreationModel.getPassword()); - } - setProxyDetails(accountCreationModel, accountDetails); - return accountDetails; - }); - createAccount(accountCreationModel, newAccount); - } - - public void initJamiAccountCreation(net.jami.mvp.AccountCreationModel accountCreationModel, String defaultAccountName) { - Single<Map<String, String>> newAccount = initRingAccountDetails(defaultAccountName) - .map(accountDetails -> { - if (!StringUtils.isEmpty(accountCreationModel.getUsername())) { - accountDetails.put(ConfigKey.ACCOUNT_REGISTERED_NAME.key(), accountCreationModel.getUsername()); - } - if (!StringUtils.isEmpty(accountCreationModel.getPassword())) { - accountDetails.put(ConfigKey.ARCHIVE_PASSWORD.key(), accountCreationModel.getPassword()); - } - setProxyDetails(accountCreationModel, accountDetails); - return accountDetails; - }); - createAccount(accountCreationModel, newAccount); - } - - public void initJamiAccountLink(net.jami.mvp.AccountCreationModel accountCreationModel, String defaultAccountName) { - Single<Map<String, String>> newAccount = initRingAccountDetails(defaultAccountName) - .map(accountDetails -> { - Settings settings = mPreferences.getSettings(); - if (settings != null && settings.isAllowPushNotifications()) { - accountCreationModel.setPush(true); - setProxyDetails(accountCreationModel, accountDetails); - } - if (!StringUtils.isEmpty(accountCreationModel.getPassword())) { - accountDetails.put(ConfigKey.ARCHIVE_PASSWORD.key(), accountCreationModel.getPassword()); - } - if (accountCreationModel.getArchive() != null) { - accountDetails.put(ConfigKey.ARCHIVE_PATH.key(), accountCreationModel.getArchive().getAbsolutePath()); - } else if (!accountCreationModel.getPin().isEmpty()) { - accountDetails.put(ConfigKey.ARCHIVE_PIN.key(), accountCreationModel.getPin()); - } - return accountDetails; - }); - createAccount(accountCreationModel, newAccount); - } - - private void createAccount(AccountCreationModel accountCreationModel, Single<Map<String, String>> details) { - mAccountCreationModel = accountCreationModel; - Observable<Account> newAccount = details.flatMapObservable(accountDetails -> createNewAccount(accountCreationModel, accountDetails)); - accountCreationModel.setAccountObservable(newAccount); - mCompositeDisposable.add(newAccount - .observeOn(mUiScheduler) - .subscribe(accountCreationModel::setNewAccount, e-> Log.e(TAG, "Can't create account", e))); - if (accountCreationModel.isLink()) { - getView().displayProgress(true); - mCompositeDisposable.add(newAccount - .filter(a -> { - String newState = a.getRegistrationState(); - return !(newState.isEmpty() || newState.contentEquals(AccountConfig.STATE_INITIALIZING)); - }) - .firstOrError() - .observeOn(mUiScheduler) - .subscribe(acc -> { - accountCreationModel.setNewAccount(acc); - net.jami.account.AccountWizardView view = getView(); - if (view != null) { - view.displayProgress(false); - String newState = acc.getRegistrationState(); - if (newState.contentEquals(AccountConfig.STATE_ERROR_GENERIC)) { - mCreatingAccount = false; - if (accountCreationModel.getArchive() == null) - view.displayCannotBeFoundError(); - else - view.displayGenericError(); - } else { - view.goToProfileCreation(accountCreationModel); - } - } - }, e -> { - mCreatingAccount = false; - getView().displayProgress(false); - getView().displayCannotBeFoundError(); - })); - } else { - getView().goToProfileCreation(accountCreationModel); - } - } - - public void successDialogClosed() { - net.jami.account.AccountWizardView view = getView(); - if (view != null) { - getView().finish(true); - } - } - - private Single<HashMap<String, String>> initRingAccountDetails(String defaultAccountName) { - return initAccountDetails().map(accountDetails -> { - accountDetails.put(ConfigKey.ACCOUNT_ALIAS.key(), mAccountService.getNewAccountName(defaultAccountName)); - accountDetails.put(ConfigKey.ACCOUNT_UPNP_ENABLE.key(), AccountConfig.TRUE_STR); - return accountDetails; - }); - } - - private Single<HashMap<String, String>> initAccountDetails() { - if (mAccountType == null) - return Single.error(new IllegalStateException()); - return mAccountService.getAccountTemplate(mAccountType) - .map(accountDetails -> { - accountDetails.put(ConfigKey.VIDEO_ENABLED.key(), Boolean.toString(true)); - accountDetails.put(ConfigKey.ACCOUNT_DTMF_TYPE.key(), "sipinfo"); - return accountDetails; - }); - } - - private Observable<Account> createNewAccount(AccountCreationModel model, Map<String, String> accountDetails) { - if (mCreatingAccount) { - return newAccount; - } - - mCreatingAccount = true; - //mCreationError = false; - - BehaviorSubject<Account> account = BehaviorSubject.create(); - account.filter(a -> { - String newState = a.getRegistrationState(); - return !(newState.isEmpty() || newState.contentEquals(AccountConfig.STATE_INITIALIZING)); - }) - .firstElement() - .subscribe(a -> { - if (!model.isLink() && a.isJami() && !StringUtils.isEmpty(model.getUsername())) - mAccountService.registerName(a, model.getPassword(), model.getUsername()); - mAccountService.setCurrentAccount(a); - if (model.isPush()) { - Settings settings = mPreferences.getSettings(); - settings.setAllowPushNotifications(true); - mPreferences.setSettings(settings); - } - }); - - mAccountService - .addAccount(accountDetails) - .subscribe(account); - - newAccount = account; - return account; - } - - public void profileCreated(net.jami.mvp.AccountCreationModel accountCreationModel, boolean saveProfile) { - getView().blockOrientation(); - getView().displayProgress(true); - - Single<Account> newAccount = mAccountCreationModel - .getAccountObservable() - .filter(a -> { - String newState = a.getRegistrationState(); - return !(newState.isEmpty() || newState.contentEquals(AccountConfig.STATE_INITIALIZING)); - }) - .firstOrError(); - - if (saveProfile) { - newAccount = newAccount.flatMap(a -> getView() - .saveProfile(a, accountCreationModel) - .map(vcard -> a)); - } - - mCompositeDisposable.add(newAccount - .observeOn(mUiScheduler) - .subscribe(account -> { - mCreatingAccount = false; - net.jami.account.AccountWizardView view = getView(); - if (view != null) { - view.displayProgress(false); - String newState = account.getRegistrationState(); - Log.w(TAG, "newState " + newState); - switch (newState) { - case AccountConfig.STATE_ERROR_GENERIC: - view.displayGenericError(); - //mCreationError = true; - break; - case AccountConfig.STATE_UNREGISTERED: - //mCreationError = false; - break; - case AccountConfig.STATE_ERROR_NETWORK: - view.displayNetworkError(); - //mCreationError = true; - break; - default: - //mCreationError = false; - break; - } - view.displaySuccessDialog(); - } - }, e -> { - mCreatingAccount = false; - //mCreationError = true; - AccountWizardView view = getView(); - if (view != null) { - view.displayGenericError(); - getView().finish(true); - } - })); - } - - public void errorDialogClosed() { - getView().goToHomeCreation(); - } -} diff --git a/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..694518510633b9b8d33875e3fd0eb6dcb63b8b48 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardPresenter.kt @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.account + +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.subjects.BehaviorSubject +import net.jami.model.Account +import net.jami.model.AccountConfig +import net.jami.model.ConfigKey +import net.jami.model.AccountCreationModel +import net.jami.mvp.RootPresenter +import net.jami.services.AccountService +import net.jami.services.DeviceRuntimeService +import net.jami.services.PreferencesService +import net.jami.utils.Log +import net.jami.utils.StringUtils.isEmpty +import java.util.* +import javax.inject.Inject +import javax.inject.Named + +class AccountWizardPresenter @Inject constructor( + private val mAccountService: AccountService, + private val mPreferences: PreferencesService, + private val mDeviceService: DeviceRuntimeService, + @param:Named("UiScheduler") private val mUiScheduler: Scheduler +) : RootPresenter<AccountWizardView>() { + //private boolean mCreationError = false; + private var mCreatingAccount = false + private var mAccountType: String? = null + private var mAccountCreationModel: AccountCreationModel? = null + private var newAccount: Observable<Account>? = null + + fun init(accountType: String) { + mAccountType = accountType + if (AccountConfig.ACCOUNT_TYPE_SIP == mAccountType) { + view?.goToSipCreation() + } else { + view?.goToHomeCreation() + } + } + + private fun setProxyDetails(accountCreationModel: AccountCreationModel, details: MutableMap<String, String>) { + if (accountCreationModel.isPush) { + details[ConfigKey.PROXY_ENABLED.key()] = AccountConfig.TRUE_STR + val pushToken = mDeviceService.pushToken + if (pushToken != null && pushToken.isNotEmpty()) + details[ConfigKey.PROXY_PUSH_TOKEN.key()] = pushToken + } + } + + fun initJamiAccountConnect(accountCreationModel: AccountCreationModel, defaultAccountName: String) { + val newAccount = initRingAccountDetails(defaultAccountName) + .map<Map<String, String>> { accountDetails -> + if (!isEmpty(accountCreationModel.managementServer)) { + accountDetails[ConfigKey.MANAGER_URI.key()] = accountCreationModel.managementServer!! + if (!isEmpty(accountCreationModel.username)) { + accountDetails[ConfigKey.MANAGER_USERNAME.key()] = accountCreationModel.username + } + } else if (!isEmpty(accountCreationModel.username)) { + accountDetails[ConfigKey.ACCOUNT_USERNAME.key()] = accountCreationModel.username + } + if (!isEmpty(accountCreationModel.password)) { + accountDetails[ConfigKey.ARCHIVE_PASSWORD.key()] = accountCreationModel.password + } + setProxyDetails(accountCreationModel, accountDetails) + accountDetails + } + createAccount(accountCreationModel, newAccount) + } + + fun initJamiAccountCreation(accountCreationModel: AccountCreationModel, defaultAccountName: String) { + val newAccount = initRingAccountDetails(defaultAccountName) + .map<Map<String, String>> { accountDetails -> + if (!isEmpty(accountCreationModel.username)) { + accountDetails[ConfigKey.ACCOUNT_REGISTERED_NAME.key()] = accountCreationModel.username + } + if (!isEmpty(accountCreationModel.password)) { + accountDetails[ConfigKey.ARCHIVE_PASSWORD.key()] = accountCreationModel.password + } + setProxyDetails(accountCreationModel, accountDetails) + accountDetails + } + createAccount(accountCreationModel, newAccount) + } + + fun initJamiAccountLink(accountCreationModel: AccountCreationModel, defaultAccountName: String) { + val newAccount = initRingAccountDetails(defaultAccountName) + .map<Map<String, String>> { accountDetails -> + val settings = mPreferences.settings + if (settings.isAllowPushNotifications) { + accountCreationModel.isPush = true + setProxyDetails(accountCreationModel, accountDetails) + } + if (!isEmpty(accountCreationModel.password)) { + accountDetails[ConfigKey.ARCHIVE_PASSWORD.key()] = accountCreationModel.password + } + if (accountCreationModel.archive != null) { + accountDetails[ConfigKey.ARCHIVE_PATH.key()] = accountCreationModel.archive!!.absolutePath + } else if (accountCreationModel.pin.isNotEmpty()) { + accountDetails[ConfigKey.ARCHIVE_PIN.key()] = accountCreationModel.pin + } + accountDetails + } + createAccount(accountCreationModel, newAccount) + } + + private fun createAccount(accountCreationModel: AccountCreationModel, details: Single<Map<String, String>>) { + mAccountCreationModel = accountCreationModel + val newAccount = details.flatMapObservable { accountDetails -> createNewAccount(accountCreationModel, accountDetails) } + accountCreationModel.accountObservable = newAccount + mCompositeDisposable.add(newAccount + .observeOn(mUiScheduler) + .subscribe({ account: Account -> accountCreationModel.newAccount = account }) + { e -> Log.e(TAG, "Can't create account", e) }) + if (accountCreationModel.isLink) { + view!!.displayProgress(true) + mCompositeDisposable.add(newAccount + .filter { a: Account -> + val newState = a.registrationState + !(newState.isEmpty() || newState.contentEquals(AccountConfig.STATE_INITIALIZING)) + } + .firstOrError() + .observeOn(mUiScheduler) + .subscribe({ acc: Account -> + accountCreationModel.newAccount = acc + val view = view + if (view != null) { + view.displayProgress(false) + val newState = acc.registrationState + if (newState.contentEquals(AccountConfig.STATE_ERROR_GENERIC)) { + mCreatingAccount = false + if (accountCreationModel.archive == null) view.displayCannotBeFoundError() else view.displayGenericError() + } else { + view.goToProfileCreation(accountCreationModel) + } + } + }) { + mCreatingAccount = false + view!!.displayProgress(false) + view!!.displayCannotBeFoundError() + }) + } else { + view?.goToProfileCreation(accountCreationModel) + } + } + + fun successDialogClosed() { + view?.finish(true) + } + + private fun initRingAccountDetails(defaultAccountName: String): Single<HashMap<String, String>> { + return initAccountDetails().map { accountDetails: HashMap<String, String> -> + accountDetails[ConfigKey.ACCOUNT_ALIAS.key()] = mAccountService.getNewAccountName(defaultAccountName) + accountDetails[ConfigKey.ACCOUNT_UPNP_ENABLE.key()] = AccountConfig.TRUE_STR + accountDetails + } + } + + private fun initAccountDetails(): Single<HashMap<String, String>> { + return if (mAccountType == null) Single.error(IllegalStateException()) + else mAccountService.getAccountTemplate(mAccountType!!) + .map { accountDetails: HashMap<String, String> -> + accountDetails[ConfigKey.VIDEO_ENABLED.key()] = true.toString() + accountDetails[ConfigKey.ACCOUNT_DTMF_TYPE.key()] = "sipinfo" + accountDetails + } + } + + private fun createNewAccount(model: AccountCreationModel, accountDetails: Map<String, String>): Observable<Account> { + if (mCreatingAccount) { + return newAccount!! + } + mCreatingAccount = true + //mCreationError = false; + val account = BehaviorSubject.create<Account>() + account.filter { a: Account -> + val newState = a.registrationState + !(newState.isEmpty() || newState.contentEquals(AccountConfig.STATE_INITIALIZING)) + } + .firstElement() + .subscribe { a: Account -> + if (!model.isLink && a.isJami && !isEmpty(model.username)) + mAccountService.registerName(a, model.password, model.username) + mAccountService.currentAccount = a + if (model.isPush) { + val settings = mPreferences.settings + settings.isAllowPushNotifications = true + mPreferences.settings = settings + } + } + mAccountService.addAccount(accountDetails) + .subscribe(account) + newAccount = account + return account + } + + fun profileCreated(accountCreationModel: AccountCreationModel, saveProfile: Boolean) { + view!!.blockOrientation() + view!!.displayProgress(true) + var newAccount = mAccountCreationModel!!.accountObservable!!.filter { a: Account -> + val newState = a.registrationState + !(newState.isEmpty() || newState.contentEquals(AccountConfig.STATE_INITIALIZING)) + } + .firstOrError() + if (saveProfile) { + newAccount = newAccount.flatMap { a: Account -> + view!!.saveProfile(a, accountCreationModel).map { a } + } + } + mCompositeDisposable.add(newAccount + .observeOn(mUiScheduler) + .subscribe({ account: Account -> + mCreatingAccount = false + val view = view + if (view != null) { + view.displayProgress(false) + val newState = account.registrationState + Log.w(TAG, "newState $newState") + when (newState) { + AccountConfig.STATE_ERROR_GENERIC -> view.displayGenericError() + AccountConfig.STATE_UNREGISTERED -> { } + AccountConfig.STATE_ERROR_NETWORK -> view.displayNetworkError() + else -> {} + } + view.displaySuccessDialog() + } + }) { e -> + Log.e(TAG, "Error creating account", e); + mCreatingAccount = false + //mCreationError = true; + val view = view + if (view != null) { + view.displayGenericError() + //view.finish(true) + } + }) + } + + fun errorDialogClosed() { + view!!.goToHomeCreation() + } + + companion object { + val TAG = AccountWizardPresenter::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardView.java b/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardView.java deleted file mode 100644 index 7d927c1a5b0b5b7e8639c9ae6fac51aa5b529dce..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardView.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.account; - -import net.jami.model.Account; -import net.jami.mvp.AccountCreationModel; -import ezvcard.VCard; -import io.reactivex.rxjava3.core.Single; - -public interface AccountWizardView { - - void goToHomeCreation(); - - void goToSipCreation(); - - void displayProgress(boolean display); - - void displayCreationError(); - - void blockOrientation(); - - void finish(boolean affinity); - - Single<VCard> saveProfile(Account account, AccountCreationModel accountCreationModel); - - void displayGenericError(); - - void displayNetworkError(); - - void displayCannotBeFoundError(); - - void displaySuccessDialog(); - - void goToProfileCreation(AccountCreationModel accountCreationModel); -} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/HomeAccountCreationPresenter.java b/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardView.kt similarity index 55% rename from ring-android/libringclient/src/main/java/net/jami/account/HomeAccountCreationPresenter.java rename to ring-android/libringclient/src/main/java/net/jami/account/AccountWizardView.kt index 8995ca935e75950a9d937dfeadab61ee0937ff17..e1fd98db10f9dc7757d6774ccb7ef5fb40f7e609 100644 --- a/ring-android/libringclient/src/main/java/net/jami/account/HomeAccountCreationPresenter.java +++ b/ring-android/libringclient/src/main/java/net/jami/account/AccountWizardView.kt @@ -17,28 +17,24 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ - -package net.jami.account; - -import javax.inject.Inject; - -import net.jami.mvp.RootPresenter; - -public class HomeAccountCreationPresenter extends RootPresenter<HomeAccountCreationView> { - - @Inject - public HomeAccountCreationPresenter() { - } - - public void clickOnCreateAccount() { - getView().goToAccountCreation(); - } - - public void clickOnLinkAccount() { - getView().goToAccountLink(); - } - - public void clickOnConnectAccount() { - getView().goToAccountConnect(); - } -} +package net.jami.account + +import ezvcard.VCard +import io.reactivex.rxjava3.core.Single +import net.jami.model.Account +import net.jami.model.AccountCreationModel + +interface AccountWizardView { + fun goToHomeCreation() + fun goToSipCreation() + fun displayProgress(display: Boolean) + fun displayCreationError() + fun blockOrientation() + fun finish(affinity: Boolean) + fun saveProfile(account: Account, accountCreationModel: AccountCreationModel): Single<VCard> + fun displayGenericError() + fun displayNetworkError() + fun displayCannotBeFoundError() + fun displaySuccessDialog() + fun goToProfileCreation(accountCreationModel: AccountCreationModel) +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountView.java b/ring-android/libringclient/src/main/java/net/jami/account/HomeAccountCreationPresenter.kt similarity index 67% rename from ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountView.java rename to ring-android/libringclient/src/main/java/net/jami/account/HomeAccountCreationPresenter.kt index a05653cdf5279bce0479655c351fe2cb9229ca2d..bf632427df8208d11fefa5560f41b4ff895f6e05 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/GeneralAccountView.java +++ b/ring-android/libringclient/src/main/java/net/jami/account/HomeAccountCreationPresenter.kt @@ -17,22 +17,21 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -package cx.ring.fragments; +package net.jami.account -import androidx.annotation.NonNull; +import net.jami.mvp.RootPresenter +import javax.inject.Inject -import net.jami.model.Account; -import net.jami.utils.Tuple; +class HomeAccountCreationPresenter @Inject constructor() : RootPresenter<HomeAccountCreationView>() { + fun clickOnCreateAccount() { + view?.goToAccountCreation() + } -public interface GeneralAccountView { + fun clickOnLinkAccount() { + view?.goToAccountLink() + } - void addJamiPreferences(String accountId); - - void addSipPreferences(); - - void accountChanged(@NonNull Account account); - - void finish(); - - void updateResolutions(Tuple<Integer, Integer> maxResolution, int currentResolution); -} + fun clickOnConnectAccount() { + view?.goToAccountConnect() + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Error.java b/ring-android/libringclient/src/main/java/net/jami/account/HomeAccountCreationView.kt similarity index 83% rename from ring-android/libringclient/src/main/java/net/jami/model/Error.java rename to ring-android/libringclient/src/main/java/net/jami/account/HomeAccountCreationView.kt index 5e7d2db3cd618209a324a089ce00a04c7e86d5aa..7114730bcd28ec75c2e7614e5981e6641bd92de9 100644 --- a/ring-android/libringclient/src/main/java/net/jami/model/Error.java +++ b/ring-android/libringclient/src/main/java/net/jami/account/HomeAccountCreationView.kt @@ -1,5 +1,3 @@ -package net.jami.model; - /* * Copyright (C) 2004-2021 Savoir-faire Linux Inc. * @@ -19,12 +17,10 @@ package net.jami.model; * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +package net.jami.account -public enum Error { - GENERIC_ERROR, - NO_MICROPHONE, - NO_INPUT, - INVALID_FILE, - NOT_ABLE_TO_WRITE_FILE, - NO_SPACE_LEFT -} +interface HomeAccountCreationView { + fun goToAccountCreation() + fun goToAccountLink() + fun goToAccountConnect() +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountConnectPresenter.java b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountConnectPresenter.java deleted file mode 100644 index 7038b2833793d6a2542f8d38f7824614639251a9..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountConnectPresenter.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.account; - -import javax.inject.Inject; - -import net.jami.mvp.AccountCreationModel; -import net.jami.mvp.RootPresenter; -import net.jami.utils.StringUtils; - -public class JamiAccountConnectPresenter extends RootPresenter<JamiConnectAccountView> { - - private AccountCreationModel mAccountCreationModel; - - @Inject - public JamiAccountConnectPresenter() { - } - - public void init(AccountCreationModel accountCreationModel) { - mAccountCreationModel = accountCreationModel; - if (mAccountCreationModel == null) { - getView().cancel(); - return; - } - /*boolean hasArchive = mAccountCreationModel.getArchive() != null; - JamiConnectAccountView view = getView(); - if (view != null) { - view.showPin(!hasArchive); - view.enableLinkButton(hasArchive); - }*/ - } - - public void passwordChanged(String password) { - mAccountCreationModel.setPassword(password); - showConnectButton(); - } - - public void usernameChanged(String username) { - mAccountCreationModel.setUsername(username); - showConnectButton(); - } - - public void serverChanged(String server) { - mAccountCreationModel.setManagementServer(server); - showConnectButton(); - } - - public void connectClicked() { - if (isFormValid()) { - getView().createAccount(mAccountCreationModel); - } - } - - private void showConnectButton() { - getView().enableConnectButton(isFormValid()); - } - - private boolean isFormValid() { - return !StringUtils.isEmpty(mAccountCreationModel.getPassword()) - && !StringUtils.isEmpty(mAccountCreationModel.getUsername()) - && !StringUtils.isEmpty(mAccountCreationModel.getManagementServer()); - } - -} diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountConnectPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountConnectPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..67d26dd4491b5d85260f2d9a4b58f251e6eedc40 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountConnectPresenter.kt @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.account + +import net.jami.model.AccountCreationModel +import net.jami.mvp.RootPresenter +import net.jami.utils.StringUtils.isEmpty +import javax.inject.Inject + +class JamiAccountConnectPresenter @Inject constructor() : RootPresenter<JamiConnectAccountView>() { + private var mAccountCreationModel: AccountCreationModel? = null + fun init(accountCreationModel: AccountCreationModel?) { + mAccountCreationModel = accountCreationModel + if (mAccountCreationModel == null) { + view?.cancel() + return + } + /*boolean hasArchive = mAccountCreationModel.getArchive() != null; + JamiConnectAccountView view = getView(); + if (view != null) { + view.showPin(!hasArchive); + view.enableLinkButton(hasArchive); + }*/ + } + + fun passwordChanged(password: String) { + mAccountCreationModel!!.password = password + showConnectButton() + } + + fun usernameChanged(username: String) { + mAccountCreationModel!!.username = username + showConnectButton() + } + + fun serverChanged(server: String?) { + mAccountCreationModel!!.managementServer = server + showConnectButton() + } + + fun connectClicked() { + if (isFormValid) { + view?.createAccount(mAccountCreationModel!!) + } + } + + private fun showConnectButton() { + view?.enableConnectButton(isFormValid) + } + + private val isFormValid: Boolean + get() = !isEmpty(mAccountCreationModel!!.password) + && !isEmpty(mAccountCreationModel!!.username) + && !isEmpty(mAccountCreationModel!!.managementServer) +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationPresenter.kt index 0b5e640c9c293453393c1b540a79672e9d049c08..6fabdd3a1031ae5363cf1d972e67bc3a88525426 100644 --- a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationPresenter.kt +++ b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationPresenter.kt @@ -22,8 +22,7 @@ package net.jami.account import io.reactivex.rxjava3.core.Scheduler import io.reactivex.rxjava3.subjects.PublishSubject -import net.jami.account.JamiAccountCreationPresenter -import net.jami.mvp.AccountCreationModel +import net.jami.model.AccountCreationModel import net.jami.mvp.RootPresenter import net.jami.services.AccountService import net.jami.services.AccountService.RegisteredName @@ -49,7 +48,7 @@ class JamiAccountCreationPresenter @Inject constructor( super.bindView(view) mCompositeDisposable.add(contactQuery .debounce(TYPING_DELAY, TimeUnit.MILLISECONDS) - .switchMapSingle { q: String? -> mAccountService.findRegistrationByName("", "", q!!) } + .switchMapSingle { q: String -> mAccountService.findRegistrationByName("", "", q) } .observeOn(mUiScheduler) .subscribe { q: RegisteredName -> onLookupResult(q.name, q.address, q.state) }) } @@ -87,7 +86,7 @@ class JamiAccountCreationPresenter @Inject constructor( } fun passwordUnset() { - if (mAccountCreationModel != null) mAccountCreationModel!!.password = null + if (mAccountCreationModel != null) mAccountCreationModel!!.password = "" isPasswordCorrect = true isConfirmCorrect = true view?.showInvalidPasswordError(false) @@ -102,62 +101,62 @@ class JamiAccountCreationPresenter @Inject constructor( fun passwordChanged(password: String) { if (mAccountCreationModel != null) mAccountCreationModel!!.password = password if (!isEmpty(password) && password.length < PASSWORD_MIN_LENGTH) { - view!!.showInvalidPasswordError(true) + view?.showInvalidPasswordError(true) isPasswordCorrect = false } else { - view!!.showInvalidPasswordError(false) - isPasswordCorrect = password.length != 0 + view?.showInvalidPasswordError(false) + isPasswordCorrect = password.isNotEmpty() isConfirmCorrect = if (!password.contentEquals(mPasswordConfirm)) { - if (mPasswordConfirm.length > 0) view!!.showNonMatchingPasswordError(true) + if (mPasswordConfirm.isNotEmpty()) + view?.showNonMatchingPasswordError(true) false } else { - view!!.showNonMatchingPasswordError(false) + view?.showNonMatchingPasswordError(false) true } } - view!!.enableNextButton(isPasswordCorrect && isConfirmCorrect) + view?.enableNextButton(isPasswordCorrect && isConfirmCorrect) } fun passwordConfirmChanged(passwordConfirm: String) { isConfirmCorrect = if (passwordConfirm != mAccountCreationModel!!.password) { - view!!.showNonMatchingPasswordError(true) + view?.showNonMatchingPasswordError(true) false } else { - view!!.showNonMatchingPasswordError(false) + view?.showNonMatchingPasswordError(false) true } mPasswordConfirm = passwordConfirm - view!!.enableNextButton(isPasswordCorrect && isConfirmCorrect) + view?.enableNextButton(isPasswordCorrect && isConfirmCorrect) } fun createAccount() { if (isInputValid) { - val view = view - view!!.goToAccountCreation(mAccountCreationModel) + view?.goToAccountCreation(mAccountCreationModel!!) } } private val isInputValid: Boolean - private get() { + get() { val passwordOk = isPasswordCorrect && isConfirmCorrect - val usernameOk = - mAccountCreationModel != null && mAccountCreationModel!!.username != null || isUsernameCorrect + val usernameOk = mAccountCreationModel?.username != null || isUsernameCorrect return passwordOk && usernameOk } private fun checkForms() { val valid = isInputValid - if (valid && isUsernameCorrect) view!!.updateUsernameAvailability(JamiAccountCreationView.UsernameAvailabilityStatus.AVAILABLE) + if (valid && isUsernameCorrect) + view?.updateUsernameAvailability(JamiAccountCreationView.UsernameAvailabilityStatus.AVAILABLE) } - private fun onLookupResult(name: String?, address: String?, state: Int) { + private fun onLookupResult(name: String, address: String?, state: Int) { val view = view //Once we get the result, we can show the loading animation again when the user types showLoadingAnimation = true if (view == null) { return } - if (name == null || name.isEmpty()) { + if (name.isEmpty()) { view.updateUsernameAvailability(JamiAccountCreationView.UsernameAvailabilityStatus.RESET) isUsernameCorrect = false } else { @@ -189,11 +188,11 @@ class JamiAccountCreationPresenter @Inject constructor( } fun setPush(push: Boolean) { - mAccountCreationModel!!.isPush = push + mAccountCreationModel?.isPush = push } companion object { - val TAG = JamiAccountCreationPresenter::class.java.simpleName + val TAG = JamiAccountCreationPresenter::class.simpleName!! private const val PASSWORD_MIN_LENGTH = 6 private const val TYPING_DELAY = 350L } diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationView.kt b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationView.kt index 1bb996865a5c5207a628813bdc629b826c7f42f4..555eca5af659e3b7f88b8b98c499c0075f9f6bda 100644 --- a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationView.kt +++ b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountCreationView.kt @@ -19,17 +19,17 @@ */ package net.jami.account -import net.jami.mvp.AccountCreationModel +import net.jami.model.AccountCreationModel interface JamiAccountCreationView { enum class UsernameAvailabilityStatus { ERROR_USERNAME_TAKEN, ERROR_USERNAME_INVALID, ERROR, LOADING, AVAILABLE, RESET } - fun updateUsernameAvailability(status: UsernameAvailabilityStatus?) + fun updateUsernameAvailability(status: UsernameAvailabilityStatus) fun showInvalidPasswordError(display: Boolean) fun showNonMatchingPasswordError(display: Boolean) fun enableNextButton(enabled: Boolean) - fun goToAccountCreation(accountCreationModel: AccountCreationModel?) + fun goToAccountCreation(accountCreationModel: AccountCreationModel) fun cancel() } \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryPresenter.java b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryPresenter.java deleted file mode 100644 index 7d76a0fdea1f9b0257e74a4bc5a9f49cc4bb6aea..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryPresenter.java +++ /dev/null @@ -1,252 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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 net.jami.account; - -import net.jami.model.Account; -import net.jami.services.AccountService; -import net.jami.services.DeviceRuntimeService; - -import java.io.File; -import java.net.SocketException; - -import javax.inject.Inject; -import javax.inject.Named; - -import net.jami.mvp.RootPresenter; -import net.jami.services.VCardService; -import net.jami.utils.Log; -import net.jami.utils.StringUtils; -import net.jami.utils.VCardUtils; - -import ezvcard.property.Photo; -import ezvcard.property.RawProperty; -import ezvcard.property.Uid; -import io.reactivex.rxjava3.core.Scheduler; -import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.schedulers.Schedulers; - -public class JamiAccountSummaryPresenter extends RootPresenter<JamiAccountSummaryView> { - - private static final String TAG = JamiAccountSummaryPresenter.class.getSimpleName(); - - private final DeviceRuntimeService mDeviceRuntimeService; - private final AccountService mAccountService; - private final VCardService mVcardService; - - private String mAccountID; - - @Inject - @Named("UiScheduler") - protected Scheduler mUiScheduler; - - @Inject - public JamiAccountSummaryPresenter(AccountService accountService, - DeviceRuntimeService deviceRuntimeService, - VCardService vcardService) { - mAccountService = accountService; - mDeviceRuntimeService = deviceRuntimeService; - mVcardService = vcardService; - } - - public void registerName(String name, String password) { - final Account account = mAccountService.getAccount(mAccountID); - if (account == null || getView() == null) { - return; - } - mAccountService.registerName(account, password, name); - getView().accountChanged(account); - } - - public void startAccountExport(String password) { - if (getView() == null) { - return; - } - getView().showExportingProgressDialog(); - mCompositeDisposable.add(mAccountService - .exportOnRing(mAccountID, password) - .observeOn(mUiScheduler) - .subscribe(pin -> getView().showPIN(pin), - error -> { - if (error instanceof IllegalArgumentException) { - getView().showPasswordError(); - } else if (error instanceof SocketException) { - getView().showNetworkError(); - } else { - getView().showGenericError(); - } - })); - } - - public void setAccountId(String accountID) { - mCompositeDisposable.clear(); - mAccountID = accountID; - JamiAccountSummaryView v = getView(); - Account account = mAccountService.getAccount(mAccountID); - if (v != null && account != null) - v.accountChanged(account); - mCompositeDisposable.add(mAccountService.getObservableAccountUpdates(mAccountID) - .observeOn(mUiScheduler) - .subscribe(a -> { - JamiAccountSummaryView view = getView(); - if (view != null) - view.accountChanged(a); - })); - } - - public void enableAccount(boolean newValue) { - Account account = mAccountService.getAccount(mAccountID); - if (account == null) { - Log.w(TAG, "account not found!"); - return; - } - - account.setEnabled(newValue); - mAccountService.setAccountEnabled(account.getAccountID(), newValue); - } - - public void changePassword(String oldPassword, String newPassword) { - JamiAccountSummaryView view = getView(); - if (view != null) - view.showPasswordProgressDialog(); - mCompositeDisposable.add(mAccountService.setAccountPassword(mAccountID, oldPassword, newPassword) - .observeOn(mUiScheduler) - .subscribe( - () -> getView().passwordChangeEnded(true), - e -> getView().passwordChangeEnded(false))); - } - - public String getDeviceName() { - Account account = mAccountService.getAccount(mAccountID); - if (account == null) { - Log.w(TAG, "account not found!"); - return null; - } - return account.getDeviceName(); - } - - public void downloadAccountsArchive(File dest, String password) { - getView().showExportingProgressDialog(); - mCompositeDisposable.add( - mAccountService.exportToFile(mAccountID, dest.getAbsolutePath(), password) - .observeOn(mUiScheduler) - .subscribe(() -> getView().displayCompleteArchive(dest), - error -> getView().passwordChangeEnded(false))); - } - - public void saveVCardFormattedName(String username) { - Account account = mAccountService.getAccount(mAccountID); - File filesDir = mDeviceRuntimeService.provideFilesDir(); - - mCompositeDisposable.add(VCardUtils.loadLocalProfileFromDiskWithDefault(filesDir, mAccountID) - .doOnSuccess(vcard -> { - vcard.setFormattedName(username); - vcard.removeProperties(RawProperty.class); - }) - .flatMap(vcard -> VCardUtils.saveLocalProfileToDisk(vcard, mAccountID, filesDir)) - .subscribeOn(Schedulers.io()) - .subscribe(vcard -> { - account.setLoadedProfile(mVcardService.loadVCardProfile(vcard).cache()); - }, e -> Log.e(TAG, "Error saving vCard !", e))); - } - - public void saveVCard(String username, Single<Photo> photo) { - Account account = mAccountService.getAccount(mAccountID); - String ringId = account.getUsername(); - File filesDir = mDeviceRuntimeService.provideFilesDir(); - mCompositeDisposable.add(Single.zip( - VCardUtils.loadLocalProfileFromDiskWithDefault(filesDir, mAccountID).subscribeOn(Schedulers.io()), - photo, (vcard, pic) -> { - vcard.setUid(new Uid(ringId)); - if (!StringUtils.isEmpty(username)) { - vcard.setFormattedName(username); - } - vcard.removeProperties(Photo.class); - vcard.addPhoto(pic); - vcard.removeProperties(RawProperty.class); - return vcard; - }) - .flatMap(vcard -> VCardUtils.saveLocalProfileToDisk(vcard, mAccountID, filesDir)) - .subscribeOn(Schedulers.io()) - .subscribe(vcard -> { - account.setLoadedProfile(mVcardService.loadVCardProfile(vcard).cache()); - }, e -> Log.e(TAG, "Error saving vCard !", e))); - } - - public void cameraClicked() { - boolean hasPermission = mDeviceRuntimeService.hasVideoPermission() && - mDeviceRuntimeService.hasWriteExternalStoragePermission(); - net.jami.account.JamiAccountSummaryView view = getView(); - if (view != null) { - if (hasPermission) { - view.gotToImageCapture(); - } else { - view.askCameraPermission(); - } - } - } - - public void galleryClicked() { - boolean hasPermission = mDeviceRuntimeService.hasGalleryPermission(); - if (hasPermission) { - getView().goToGallery(); - } else { - getView().askGalleryPermission(); - } - } - - public void goToAccount() { - getView().goToAccount(mAccountID); - } - - public void goToMedia() { - getView().goToMedia(mAccountID); - } - - public void goToSystem() { - getView().goToSystem(mAccountID); - } - - public void goToAdvanced() { - getView().goToAdvanced(mAccountID); - } - - public void revokeDevice(final String deviceId, String password) { - if (getView() != null) { - getView().showRevokingProgressDialog(); - } - mCompositeDisposable.add(mAccountService - .revokeDevice(mAccountID, password, deviceId) - .observeOn(mUiScheduler) - .subscribe(result -> { - getView().deviceRevocationEnded(deviceId, result); - getView().updateDeviceList(mAccountService.getAccount(mAccountID).getDevices(), - mAccountService.getAccount(mAccountID).getDeviceId()); - })); - } - - public void renameDevice(String newName) { - mAccountService.renameDevice(mAccountID, newName); - } - - public Account getAccount() { - return mAccountService.getAccount(mAccountID); - } -} diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..e74d225b6b79476aa87579d015788801da08e328 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryPresenter.kt @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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 net.jami.account + +import ezvcard.VCard +import ezvcard.property.Photo +import ezvcard.property.RawProperty +import ezvcard.property.Uid +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.schedulers.Schedulers +import net.jami.model.Account +import net.jami.mvp.RootPresenter +import net.jami.services.AccountService +import net.jami.services.DeviceRuntimeService +import net.jami.services.VCardService +import net.jami.utils.Log +import net.jami.utils.StringUtils +import net.jami.utils.VCardUtils +import java.io.File +import java.net.SocketException +import javax.inject.Inject +import javax.inject.Named + +class JamiAccountSummaryPresenter @Inject constructor( + private val mAccountService: AccountService, + private val mDeviceRuntimeService: DeviceRuntimeService, + private val mVcardService: VCardService, + @param:Named("UiScheduler") private val mUiScheduler: Scheduler +) : RootPresenter<JamiAccountSummaryView>() { + private var mAccountID: String? = null + + fun registerName(name: String?, password: String?) { + val account = mAccountService.getAccount(mAccountID) ?: return + mAccountService.registerName(account, password, name) + //view?.accountChanged(account, a.second) + } + + fun startAccountExport(password: String?) { + if (view == null || mAccountID == null) { + return + } + view?.showExportingProgressDialog() + mCompositeDisposable.add(mAccountService + .exportOnRing(mAccountID!!, password!!) + .observeOn(mUiScheduler) + .subscribe({ pin: String -> view?.showPIN(pin) }) { error: Throwable -> + when (error) { + is IllegalArgumentException -> view?.showPasswordError() + is SocketException -> view?.showNetworkError() + else -> view?.showGenericError() + } + }) + } + + fun setAccountId(accountId: String) { + mCompositeDisposable.clear() + mAccountID = accountId + mCompositeDisposable.add(mAccountService.getObservableAccountProfile(accountId) + .observeOn(mUiScheduler) + .subscribe { a -> view?.accountChanged(a.first, a.second) }) + } + + fun enableAccount(newValue: Boolean) { + val account = mAccountService.getAccount(mAccountID) + if (account == null) { + Log.w(TAG, "account not found!") + return + } + account.isEnabled = newValue + mAccountService.setAccountEnabled(account.accountID, newValue) + } + + fun changePassword(oldPassword: String, newPassword: String) { + view?.showPasswordProgressDialog() + mCompositeDisposable.add(mAccountService.setAccountPassword(mAccountID!!, oldPassword, newPassword) + .observeOn(mUiScheduler) + .subscribe({ view?.passwordChangeEnded(true) }) + { view?.passwordChangeEnded(false) }) + } + + val deviceName: String? + get() { + val account = mAccountService.getAccount(mAccountID) + if (account == null) { + Log.w(TAG, "account not found!") + return null + } + return account.deviceName + } + + fun downloadAccountsArchive(dest: File, password: String?) { + view?.showExportingProgressDialog() + mCompositeDisposable.add( + mAccountService.exportToFile(mAccountID!!, dest.absolutePath, password!!) + .observeOn(mUiScheduler) + .subscribe({ view?.displayCompleteArchive(dest) }) + { view?.passwordChangeEnded(false) }) + } + + fun saveVCardFormattedName(username: String?) { + val accountId = mAccountID ?: return + val account = mAccountService.getAccount(accountId) + val filesDir = mDeviceRuntimeService.provideFilesDir() + mCompositeDisposable.add(VCardUtils.loadLocalProfileFromDiskWithDefault(filesDir, accountId) + .doOnSuccess { vcard: VCard -> + val previousName = vcard.formattedName?.value + if (StringUtils.isEmpty(previousName) == StringUtils.isEmpty(username) || previousName == username) + throw IllegalArgumentException("Name didn't change") + vcard.setFormattedName(username) + vcard.removeProperties(RawProperty::class.java) + account?.loadedProfile = mVcardService.loadVCardProfile(vcard).cache() + } + .flatMap { vcard: VCard -> VCardUtils.saveLocalProfileToDisk(vcard, accountId, filesDir) } + .subscribeOn(Schedulers.io()) + .subscribe({}) { e: Throwable -> Log.e(TAG, "Error saving vCard " + e.message) }) + } + + fun saveVCard(username: String?, photo: Single<Photo>) { + val accountId = mAccountID ?: return + val account = mAccountService.getAccount(accountId)!! + val ringId = account.username + val filesDir = mDeviceRuntimeService.provideFilesDir() + mCompositeDisposable.add(Single.zip( + VCardUtils.loadLocalProfileFromDiskWithDefault(filesDir, accountId).subscribeOn(Schedulers.io()), + photo, { vcard: VCard, pic: Photo -> + vcard.uid = Uid(ringId) + if (!StringUtils.isEmpty(username)) { + vcard.setFormattedName(username) + } + vcard.removeProperties(Photo::class.java) + vcard.addPhoto(pic) + vcard.removeProperties(RawProperty::class.java) + vcard + }) + .flatMap { vcard: VCard -> VCardUtils.saveLocalProfileToDisk(vcard, accountId, filesDir) } + .subscribeOn(Schedulers.io()) + .subscribe({ vcard: VCard -> account.loadedProfile = mVcardService.loadVCardProfile(vcard).cache() }) + { e: Throwable -> Log.e(TAG, "Error saving vCard !", e) }) + } + + fun cameraClicked() { + val hasPermission = mDeviceRuntimeService.hasVideoPermission() && mDeviceRuntimeService.hasWriteExternalStoragePermission() + val view = view + if (view != null) { + if (hasPermission) { + view.gotToImageCapture() + } else { + view.askCameraPermission() + } + } + } + + fun galleryClicked() { + val hasPermission = mDeviceRuntimeService.hasGalleryPermission() + if (hasPermission) { + view!!.goToGallery() + } else { + view!!.askGalleryPermission() + } + } + + fun goToAccount() { + view?.goToAccount(mAccountID!!) + } + + fun goToMedia() { + view?.goToMedia(mAccountID!!) + } + + fun goToSystem() { + view?.goToSystem(mAccountID!!) + } + + fun goToAdvanced() { + view?.goToAdvanced(mAccountID!!) + } + + fun revokeDevice(deviceId: String?, password: String?) { + view?.showRevokingProgressDialog() + mCompositeDisposable.add(mAccountService + .revokeDevice(mAccountID!!, password!!, deviceId!!) + .observeOn(mUiScheduler) + .subscribe { result: Int -> + val account = mAccountService.getAccount(mAccountID)!! + view?.deviceRevocationEnded(deviceId, result) + view?.updateDeviceList(account.devices, account.deviceId) + }) + } + + fun renameDevice(newName: String) { + mAccountService.renameDevice(mAccountID!!, newName) + } + + val account: Account? + get() = mAccountService.getAccount(mAccountID) + + companion object { + private val TAG = JamiAccountSummaryPresenter::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryView.java b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryView.java deleted file mode 100644 index 41c1d6b55d2aa1f0e33d3ada2cf6e7212307689a..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryView.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.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 net.jami.account; - -import java.io.File; -import java.util.Map; - -import net.jami.model.Account; - -public interface JamiAccountSummaryView { - - void showExportingProgressDialog(); - - void showPasswordProgressDialog(); - - void accountChanged(final Account account); - - void showNetworkError(); - - void showPasswordError(); - - void showGenericError(); - - void showPIN(String pin); - - void passwordChangeEnded(boolean ok); - - void displayCompleteArchive(File dest); - - void gotToImageCapture(); - - void askCameraPermission(); - - void goToGallery(); - - void askGalleryPermission(); - - void updateUserView(Account account); - - void goToMedia(String accountId); - - void goToSystem(String accountId); - - void goToAdvanced(String accountId); - - void goToAccount(String accountId); - - void setSwitchStatus(Account account); - - void showRevokingProgressDialog(); - - void deviceRevocationEnded(String device, int status); - - void updateDeviceList(Map<String, String> devices, String currentDeviceId); - -} diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryView.kt b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryView.kt new file mode 100644 index 0000000000000000000000000000000000000000..443bdac94df6c97e0a25f7b8c62ec42b882fd5cb --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/account/JamiAccountSummaryView.kt @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.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 net.jami.account + +import net.jami.model.Account +import net.jami.model.Profile +import java.io.File + +interface JamiAccountSummaryView { + fun showExportingProgressDialog() + fun showPasswordProgressDialog() + fun accountChanged(account: Account, profile: Profile) + fun showNetworkError() + fun showPasswordError() + fun showGenericError() + fun showPIN(pin: String) + fun passwordChangeEnded(ok: Boolean) + fun displayCompleteArchive(dest: File) + fun gotToImageCapture() + fun askCameraPermission() + fun goToGallery() + fun askGalleryPermission() + fun updateUserView(account: Account, profile: Profile) + fun goToMedia(accountId: String) + fun goToSystem(accountId: String) + fun goToAdvanced(accountId: String) + fun goToAccount(accountId: String) + fun setSwitchStatus(account: Account) + fun showRevokingProgressDialog() + fun deviceRevocationEnded(device: String, status: Int) + fun updateDeviceList(devices: Map<String, String>, currentDeviceId: String) +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountView.java b/ring-android/libringclient/src/main/java/net/jami/account/JamiConnectAccountView.kt similarity index 78% rename from ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountView.java rename to ring-android/libringclient/src/main/java/net/jami/account/JamiConnectAccountView.kt index e1649455d9e9e694c516c030d194c3105f74507c..98851bd07e3e02ea22f0ad0e39dd09cd99ba76a0 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/AdvancedAccountView.java +++ b/ring-android/libringclient/src/main/java/net/jami/account/JamiConnectAccountView.kt @@ -17,14 +17,12 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -package cx.ring.fragments; +package net.jami.account -import java.util.ArrayList; +import net.jami.model.AccountCreationModel -import net.jami.model.AccountConfig; - -public interface AdvancedAccountView { - - void initView(AccountConfig config, ArrayList<CharSequence> networkInterfaces); - -} +interface JamiConnectAccountView { + fun enableConnectButton(enable: Boolean) + fun createAccount(accountCreationModel: AccountCreationModel) + fun cancel() +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiLinkAccountPresenter.java b/ring-android/libringclient/src/main/java/net/jami/account/JamiLinkAccountPresenter.java deleted file mode 100644 index ac5a0746b1c4d7ce5a4d04652e36a2acabfd51db..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/account/JamiLinkAccountPresenter.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.account; - -import javax.inject.Inject; - -import net.jami.mvp.AccountCreationModel; -import net.jami.mvp.RootPresenter; - -public class JamiLinkAccountPresenter extends RootPresenter<net.jami.account.JamiLinkAccountView> { - - private AccountCreationModel mAccountCreationModel; - - @Inject - public JamiLinkAccountPresenter() { - } - - public void init(AccountCreationModel accountCreationModel) { - mAccountCreationModel = accountCreationModel; - if (mAccountCreationModel == null) { - getView().cancel(); - return; - } - - boolean hasArchive = mAccountCreationModel.getArchive() != null; - JamiLinkAccountView view = getView(); - if (view != null) { - view.showPin(!hasArchive); - view.enableLinkButton(hasArchive); - } - } - - public void passwordChanged(String password) { - if (mAccountCreationModel != null) - mAccountCreationModel.setPassword(password); - showHideLinkButton(); - } - - public void pinChanged(String pin) { - if (mAccountCreationModel != null) - mAccountCreationModel.setPin(pin); - showHideLinkButton(); - } - - public void linkClicked() { - if (isFormValid()) { - getView().createAccount(mAccountCreationModel); - } - } - - private void showHideLinkButton() { - getView().enableLinkButton(isFormValid()); - } - - private boolean isFormValid() { - return mAccountCreationModel.getArchive() != null || !mAccountCreationModel.getPin().isEmpty(); - } -} diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiLinkAccountPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/account/JamiLinkAccountPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..4253d2d73c0c8a5a34249c9f5a5fefb46cec48c2 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/account/JamiLinkAccountPresenter.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.account + +import net.jami.model.AccountCreationModel +import net.jami.mvp.RootPresenter +import javax.inject.Inject + +class JamiLinkAccountPresenter @Inject constructor() : RootPresenter<JamiLinkAccountView>() { + private var mAccountCreationModel: AccountCreationModel? = null + + fun init(accountCreationModel: AccountCreationModel?) { + mAccountCreationModel = accountCreationModel + if (mAccountCreationModel == null) { + view?.cancel() + return + } + val hasArchive = mAccountCreationModel?.archive != null + val view = view + if (view != null) { + view.showPin(!hasArchive) + view.enableLinkButton(hasArchive) + } + } + + fun passwordChanged(password: String) { + mAccountCreationModel?.password = password + showHideLinkButton() + } + + fun pinChanged(pin: String) { + mAccountCreationModel?.pin = pin + showHideLinkButton() + } + + fun linkClicked() { + if (isFormValid) { + view?.createAccount(mAccountCreationModel!!) + } + } + + private fun showHideLinkButton() { + view?.enableLinkButton(isFormValid) + } + + private val isFormValid: Boolean + get() = mAccountCreationModel?.archive != null || mAccountCreationModel!!.pin.isNotEmpty() +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiConnectAccountView.java b/ring-android/libringclient/src/main/java/net/jami/account/JamiLinkAccountView.kt similarity index 76% rename from ring-android/libringclient/src/main/java/net/jami/account/JamiConnectAccountView.java rename to ring-android/libringclient/src/main/java/net/jami/account/JamiLinkAccountView.kt index 1c07bf5f7e32cb52bf429455fd19fca195f7ad18..ccb856330b478a733534d7e77f40a371551c700f 100644 --- a/ring-android/libringclient/src/main/java/net/jami/account/JamiConnectAccountView.java +++ b/ring-android/libringclient/src/main/java/net/jami/account/JamiLinkAccountView.kt @@ -17,15 +17,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -package net.jami.account; +package net.jami.account -import net.jami.mvp.AccountCreationModel; +import net.jami.model.AccountCreationModel -public interface JamiConnectAccountView { - - void enableConnectButton(boolean enable); - - void createAccount(AccountCreationModel accountCreationModel); - - void cancel(); -} +interface JamiLinkAccountView { + fun enableLinkButton(enable: Boolean) + fun showPin(show: Boolean) + fun createAccount(accountCreationModel: AccountCreationModel) + fun cancel() +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/LinkDevicePresenter.java b/ring-android/libringclient/src/main/java/net/jami/account/LinkDevicePresenter.java deleted file mode 100644 index 9c914ad711ce84c28990e20fdc12afceef49640f..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/account/LinkDevicePresenter.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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 net.jami.account; - -import net.jami.services.AccountService; - -import java.net.SocketException; - -import javax.inject.Inject; -import javax.inject.Named; - -import net.jami.model.Account; -import net.jami.mvp.RootPresenter; - -import io.reactivex.rxjava3.core.Scheduler; - -public class LinkDevicePresenter extends RootPresenter<LinkDeviceView> { - - private static final String TAG = LinkDevicePresenter.class.getSimpleName(); - - private net.jami.services.AccountService mAccountService; - private String mAccountID; - - @Inject - @Named("UiScheduler") - protected Scheduler mUiScheduler; - - @Inject - public LinkDevicePresenter(AccountService accountService) { - mAccountService = accountService; - } - - public void startAccountExport(String password) { - if (getView() == null) { - return; - } - getView().showExportingProgress(); - mCompositeDisposable.add(mAccountService - .exportOnRing(mAccountID, password) - .observeOn(mUiScheduler) - .subscribe(pin -> getView().showPIN(pin), - error -> { - getView().dismissExportingProgress(); - if (error instanceof IllegalArgumentException) { - getView().showPasswordError(); - } else if (error instanceof SocketException) { - getView().showNetworkError(); - } else { - getView().showGenericError(); - } - })); - } - - public void setAccountId(String accountID) { - mCompositeDisposable.clear(); - mAccountID = accountID; - LinkDeviceView v = getView(); - Account account = mAccountService.getAccount(mAccountID); - if (v != null && account != null) - v.accountChanged(account); - mCompositeDisposable.add(mAccountService.getObservableAccountUpdates(mAccountID) - .observeOn(mUiScheduler) - .subscribe(a -> { - LinkDeviceView view = getView(); - if (view != null) - view.accountChanged(a); - })); - } - -} diff --git a/ring-android/libringclient/src/main/java/net/jami/account/LinkDevicePresenter.kt b/ring-android/libringclient/src/main/java/net/jami/account/LinkDevicePresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..f8872bb929c2ad4bff5f6e5a80cd7af643166c9b --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/account/LinkDevicePresenter.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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 net.jami.account + +import io.reactivex.rxjava3.core.Scheduler +import net.jami.model.Account +import net.jami.mvp.RootPresenter +import net.jami.services.AccountService +import java.net.SocketException +import javax.inject.Inject +import javax.inject.Named + +class LinkDevicePresenter @Inject constructor( + private val mAccountService: AccountService, + @Named("UiScheduler") + private var mUiScheduler: Scheduler +) : RootPresenter<LinkDeviceView>() { + private var mAccountID: String? = null + + fun startAccountExport(password: String?) { + if (view == null) { + return + } + view?.showExportingProgress() + mCompositeDisposable.add(mAccountService + .exportOnRing(mAccountID!!, password!!) + .observeOn(mUiScheduler) + .subscribe({ pin: String -> view?.showPIN(pin) }) + { error: Throwable -> + view?.dismissExportingProgress() + when (error) { + is IllegalArgumentException -> view?.showPasswordError() + is SocketException -> view?.showNetworkError() + else -> view?.showGenericError() + } + }) + } + + fun setAccountId(accountID: String) { + mCompositeDisposable.clear() + mAccountID = accountID + val account = mAccountService.getAccount(accountID) + if (account != null) + view?.accountChanged(account) + mCompositeDisposable.add(mAccountService.getObservableAccountUpdates(accountID) + .observeOn(mUiScheduler) + .subscribe { a: Account -> view?.accountChanged(a) }) + } + + companion object { + private val TAG = LinkDevicePresenter::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/LinkDeviceView.java b/ring-android/libringclient/src/main/java/net/jami/account/LinkDeviceView.kt similarity index 70% rename from ring-android/libringclient/src/main/java/net/jami/account/LinkDeviceView.java rename to ring-android/libringclient/src/main/java/net/jami/account/LinkDeviceView.kt index 120f2d0289e2c1f4101014badc3ccf0caaf5b12f..9374717dc6d6f6d512bf544e35977e88853ad56f 100644 --- a/ring-android/libringclient/src/main/java/net/jami/account/LinkDeviceView.java +++ b/ring-android/libringclient/src/main/java/net/jami/account/LinkDeviceView.kt @@ -16,25 +16,16 @@ * 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 net.jami.account; - -import net.jami.model.Account; - -public interface LinkDeviceView { - - void showExportingProgress(); - - void dismissExportingProgress(); - - void accountChanged(final Account account); - - void showNetworkError(); - - void showPasswordError(); - - void showGenericError(); - - void showPIN(String pin); - -} +package net.jami.account + +import net.jami.model.Account + +interface LinkDeviceView { + fun showExportingProgress() + fun dismissExportingProgress() + fun accountChanged(account: Account?) + fun showNetworkError() + fun showPasswordError() + fun showGenericError() + fun showPIN(pin: String) +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationPresenter.java b/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationPresenter.java deleted file mode 100644 index ef03a12ce72d3415d16404336fc8acd89f1d0722..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationPresenter.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.account; - -import net.jami.mvp.AccountCreationModel; - -import javax.inject.Inject; -import javax.inject.Named; - -import net.jami.mvp.RootPresenter; -import net.jami.services.DeviceRuntimeService; -import net.jami.services.HardwareService; -import net.jami.utils.Log; - -import io.reactivex.rxjava3.core.Scheduler; -import io.reactivex.rxjava3.core.Single; - -public class ProfileCreationPresenter extends RootPresenter<net.jami.account.ProfileCreationView> { - - public static final String TAG = ProfileCreationPresenter.class.getSimpleName(); - - private final DeviceRuntimeService mDeviceRuntimeService; - private final HardwareService mHardwareService; - private final Scheduler mUiScheduler; - - private net.jami.mvp.AccountCreationModel mAccountCreationModel; - - @Inject - public ProfileCreationPresenter(DeviceRuntimeService deviceRuntimeService, - HardwareService hardwareService, - @Named("UiScheduler") Scheduler uiScheduler) { - mDeviceRuntimeService = deviceRuntimeService; - mHardwareService = hardwareService; - mUiScheduler = uiScheduler; - } - - public void initPresenter(AccountCreationModel accountCreationModel) { - Log.w(TAG, "initPresenter"); - mAccountCreationModel = accountCreationModel; - if (mDeviceRuntimeService.hasContactPermission()) { - String profileName = mDeviceRuntimeService.getProfileName(); - if (profileName != null) { - getView().displayProfileName(profileName); - } - } else { - Log.d(TAG, "READ_CONTACTS permission is not granted."); - } - mCompositeDisposable.add(accountCreationModel - .getProfileUpdates() - .observeOn(mUiScheduler) - .subscribe(model -> { - ProfileCreationView view = getView(); - if (view != null) - view.setProfile(model); - })); - } - - public void fullNameUpdated(String fullName) { - if (mAccountCreationModel != null) - mAccountCreationModel.setFullName(fullName); - } - - public void photoUpdated(Single<Object> bitmap) { - mCompositeDisposable.add(bitmap - .subscribe(b -> mAccountCreationModel.setPhoto(b), - e -> Log.e(TAG, "Can't load image", e))); - } - - public void galleryClick() { - boolean hasPermission = mDeviceRuntimeService.hasGalleryPermission(); - if (hasPermission) { - getView().goToGallery(); - } else { - getView().askStoragePermission(); - } - } - - public void cameraClick() { - boolean hasPermission = mDeviceRuntimeService.hasVideoPermission() && - mDeviceRuntimeService.hasWriteExternalStoragePermission(); - if (hasPermission) { - getView().goToPhotoCapture(); - } else { - getView().askPhotoPermission(); - } - } - - public void cameraPermissionChanged(boolean isGranted) { - if (isGranted && mHardwareService.isVideoAvailable()) { - mHardwareService.initVideo() - .onErrorComplete() - .subscribe(); - } - } - - public void nextClick() { - getView().goToNext(mAccountCreationModel, true); - } - - public void skipClick() { - getView().goToNext(mAccountCreationModel, false); - } -} diff --git a/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..00e94eb7b3505a7d5608c78fafefe79d45ae78a9 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationPresenter.kt @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.account + +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single +import net.jami.model.AccountCreationModel +import net.jami.mvp.RootPresenter +import net.jami.services.DeviceRuntimeService +import net.jami.services.HardwareService +import net.jami.utils.Log +import javax.inject.Inject +import javax.inject.Named + +class ProfileCreationPresenter @Inject constructor( + private val mDeviceRuntimeService: DeviceRuntimeService, + private val mHardwareService: HardwareService, + @param:Named("UiScheduler") private val mUiScheduler: Scheduler +) : RootPresenter<ProfileCreationView>() { + private var mAccountCreationModel: AccountCreationModel? = null + + fun initPresenter(accountCreationModel: AccountCreationModel) { + Log.w(TAG, "initPresenter") + mAccountCreationModel = accountCreationModel + if (mDeviceRuntimeService.hasContactPermission()) { + val profileName = mDeviceRuntimeService.profileName + if (profileName != null) + view?.displayProfileName(profileName) + } else { + Log.d(TAG, "READ_CONTACTS permission is not granted.") + } + mCompositeDisposable.add(accountCreationModel + .profileUpdates + .observeOn(mUiScheduler) + .subscribe { model -> view?.setProfile(model) }) + } + + fun fullNameUpdated(fullName: String) { + mAccountCreationModel?.fullName = fullName + } + + fun photoUpdated(bitmap: Single<Any>) { + mCompositeDisposable.add(bitmap + .subscribe({ b: Any -> mAccountCreationModel?.photo = b }) + { e: Throwable -> Log.e(TAG, "Can't load image", e) }) + } + + fun galleryClick() { + val hasPermission = mDeviceRuntimeService.hasGalleryPermission() + if (hasPermission) { + view?.goToGallery() + } else { + view?.askStoragePermission() + } + } + + fun cameraClick() { + val hasPermission = mDeviceRuntimeService.hasVideoPermission() && + mDeviceRuntimeService.hasWriteExternalStoragePermission() + if (hasPermission) { + view?.goToPhotoCapture() + } else { + view?.askPhotoPermission() + } + } + + fun cameraPermissionChanged(isGranted: Boolean) { + if (isGranted && mHardwareService.isVideoAvailable) { + mHardwareService.initVideo() + .onErrorComplete() + .subscribe() + } + } + + fun nextClick() { + view?.goToNext(mAccountCreationModel!!, true) + } + + fun skipClick() { + view?.goToNext(mAccountCreationModel!!, false) + } + + companion object { + val TAG = ProfileCreationPresenter::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationView.java b/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationView.java deleted file mode 100644 index 3a3aee450f7951b1367e32402efb9e59db87ca25..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationView.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.account; - -import net.jami.mvp.AccountCreationModel; - -public interface ProfileCreationView { - - void displayProfileName(String profileName); - - void goToGallery(); - - void goToPhotoCapture(); - - void askStoragePermission(); - - void askPhotoPermission(); - - void goToNext(AccountCreationModel accountCreationModel, boolean saveProfile); - - void setProfile(AccountCreationModel model); -} diff --git a/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountView.java b/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationView.kt similarity index 68% rename from ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountView.java rename to ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationView.kt index 428cb98239fe73c90e0c0e51a3d3473b2c3e4cdb..e8067193ac428a47ce1374c70963bbd50a26733d 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/SecurityAccountView.java +++ b/ring-android/libringclient/src/main/java/net/jami/account/ProfileCreationView.kt @@ -17,19 +17,16 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -package cx.ring.fragments; +package net.jami.account -import java.util.ArrayList; +import net.jami.model.AccountCreationModel -import net.jami.model.AccountConfig; -import net.jami.model.AccountCredentials; - - -public interface SecurityAccountView { - - void removeAllCredentials(); - - void addAllCredentials(ArrayList<AccountCredentials> credentials); - - void setDetails(AccountConfig config, String[] tlsMethods); -} +interface ProfileCreationView { + fun displayProfileName(profileName: String) + fun goToGallery() + fun goToPhotoCapture() + fun askStoragePermission() + fun askPhotoPermission() + fun goToNext(accountCreationModel: AccountCreationModel, saveProfile: Boolean) + fun setProfile(model: AccountCreationModel) +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/SIPCreationPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/account/SIPCreationPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..3ba0d12133e63cf4e4c572fc186e35516479e29a --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/account/SIPCreationPresenter.kt @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.account + +import ezvcard.VCard +import ezvcard.property.FormattedName +import ezvcard.property.RawProperty +import ezvcard.property.Uid +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.observers.DisposableObserver +import net.jami.model.Account +import net.jami.model.AccountConfig +import net.jami.model.ConfigKey +import net.jami.model.Profile +import net.jami.mvp.RootPresenter +import net.jami.services.AccountService +import net.jami.services.DeviceRuntimeService +import net.jami.services.VCardService +import net.jami.utils.Log +import net.jami.utils.VCardUtils +import java.util.* +import javax.inject.Inject +import javax.inject.Named + +class SIPCreationPresenter @Inject constructor( + private val mAccountService: AccountService, + private val mDeviceService: DeviceRuntimeService, + @Named("UiScheduler") + private var mUiScheduler: Scheduler +) : RootPresenter<SIPCreationView>() { + private var mAccount: Account? = null + + /** + * Attempts to register the account specified by the form. If there are form errors (invalid or missing fields, etc.), the + * errors are presented and no actual creation attempt is made. + * + * @param hostname hostname account value + * @param username username account value + * @param password password account value + * @param bypassWarning Report eventual warning to the user + */ + fun startCreation(hostname: String?, proxy: String?, username: String?, password: String?, bypassWarning: Boolean) { + view?.resetErrors() + + // Store values at the time of the login attempt. + var warningIPAccount = false + if (hostname != null && hostname.isEmpty()) { + warningIPAccount = true + } + if (!warningIPAccount && (password == null || password.trim { it <= ' ' }.isEmpty())) { + view?.showPasswordError() + return + } + if (!warningIPAccount && (username == null || username.trim { it <= ' ' }.isEmpty())) { + view?.showUsernameError() + return + } + if (warningIPAccount && !bypassWarning) { + view?.showIP2IPWarning() + } else { + val accountDetails = initAccountDetails() + if (username != null) + accountDetails[ConfigKey.ACCOUNT_ALIAS.key()] = username + if (hostname != null && hostname.isNotEmpty()) { + accountDetails[ConfigKey.ACCOUNT_HOSTNAME.key()] = hostname + if (proxy != null) + accountDetails[ConfigKey.ACCOUNT_ROUTESET.key()] = proxy + if (username != null) + accountDetails[ConfigKey.ACCOUNT_USERNAME.key()] = username + if (password != null) + accountDetails[ConfigKey.ACCOUNT_PASSWORD.key()] = password + } + registerAccount(accountDetails) + } + } + + fun removeAccount() { + mAccount?.let { account -> + mAccountService.removeAccount(account.accountID) + mAccount = null + } + } + + private fun registerAccount(accountDetails: Map<String, String>) { + view?.showLoading() + mCompositeDisposable.add( + mAccountService.addAccount(accountDetails) + .observeOn(mUiScheduler) + .subscribeWith(object : DisposableObserver<Account>() { + override fun onNext(account: Account) { + mAccount = account + when (account.registrationState) { + AccountConfig.STATE_REGISTERED, AccountConfig.STATE_SUCCESS, AccountConfig.STATE_READY -> { + saveProfile(account.accountID) + view?.showRegistrationSuccess() + dispose() + } + AccountConfig.STATE_ERROR_NETWORK -> { + view?.showRegistrationNetworkError() + dispose() + } + AccountConfig.STATE_TRYING, AccountConfig.STATE_UNREGISTERED -> return + else -> { + view?.showRegistrationError() + dispose() + } + } + } + + override fun onError(e: Throwable) { + view?.showRegistrationError() + dispose() + } + + override fun onComplete() { + dispose() + } + }) + ) + } + + private fun initAccountDetails(): MutableMap<String, String> { + val accountDetails: MutableMap<String, String> = + mAccountService.getAccountTemplate(AccountConfig.ACCOUNT_TYPE_SIP).blockingGet() + for ((key, value) in accountDetails) { + Log.d(TAG, "Default account detail: $key -> $value") + } + accountDetails[ConfigKey.VIDEO_ENABLED.key()] = true.toString() + + //~ Sipinfo is forced for any sipaccount since overrtp is not supported yet. + //~ This will have to be removed when it will be supported. + accountDetails[ConfigKey.ACCOUNT_DTMF_TYPE.key()] = "sipinfo" + return accountDetails + } + + private fun saveProfile(accountID: String) { + val account = mAccount ?: return + val vcard = VCard() + var formattedName = account.username + if (formattedName == null || formattedName.isEmpty()) { + formattedName = account.alias + } + vcard.formattedName = FormattedName(formattedName) + val vcardUid = formattedName + accountID + vcard.uid = Uid(vcardUid) + vcard.removeProperties(RawProperty::class.java) + VCardUtils.saveLocalProfileToDisk(vcard, accountID, mDeviceService.provideFilesDir()) + .subscribe() + account.loadedProfile = Single.just(Profile(formattedName, null)) + } + + companion object { + private val TAG = SIPCreationPresenter::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/mvp/SIPCreationView.java b/ring-android/libringclient/src/main/java/net/jami/account/SIPCreationView.kt similarity index 72% rename from ring-android/libringclient/src/main/java/net/jami/mvp/SIPCreationView.java rename to ring-android/libringclient/src/main/java/net/jami/account/SIPCreationView.kt index 06dc43fdd4a7a63ce85ffbc7239853fc88d52673..d5a560cae8b0b4486dc3247f28633e6e033b6e77 100644 --- a/ring-android/libringclient/src/main/java/net/jami/mvp/SIPCreationView.java +++ b/ring-android/libringclient/src/main/java/net/jami/account/SIPCreationView.kt @@ -17,23 +17,15 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -package net.jami.mvp; - -public interface SIPCreationView { - - void showUsernameError(); - - void showLoading(); - - void resetErrors(); - - void showPasswordError(); - - void showIP2IPWarning(); - - void showRegistrationNetworkError(); - - void showRegistrationError(); - - void showRegistrationSuccess(); -} +package net.jami.account + +interface SIPCreationView { + fun showUsernameError() + fun showLoading() + fun resetErrors() + fun showPasswordError() + fun showIP2IPWarning() + fun showRegistrationNetworkError() + fun showRegistrationError() + fun showRegistrationSuccess() +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/SecurityAccountPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/account/SecurityAccountPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..8045aff302908ce59dda149bc906ae96f470f528 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/account/SecurityAccountPresenter.kt @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.account + +import net.jami.model.Account +import net.jami.model.AccountCredentials +import net.jami.model.ConfigKey +import net.jami.mvp.RootPresenter +import net.jami.services.AccountService +import javax.inject.Inject + +class SecurityAccountPresenter @Inject constructor(private val mAccountService: AccountService) : + RootPresenter<SecurityAccountView>() { + + private var mAccount: Account? = null + + fun init(accountId: String) { + val account = mAccountService.getAccount(accountId) + mAccount = account + if (account != null) { + val view = view ?: return + view.removeAllCredentials() + view.addAllCredentials(account.credentials) + val methods = mAccountService.tlsSupportedMethods + val tlsMethods = methods.toTypedArray() + view.setDetails(account.config, tlsMethods) + } + } + + fun credentialEdited(old: AccountCredentials, newCreds: AccountCredentials?) { + mAccount!!.removeCredential(old) + if (newCreds != null) { + // There is a new value for this credentials it means it has been edited (otherwise deleted) + mAccount!!.addCredential(newCreds) + } + mAccountService.setCredentials(mAccount!!.accountID, mAccount!!.credentialsHashMapList) + mAccountService.setAccountDetails(mAccount!!.accountID, mAccount!!.details) + view!!.removeAllCredentials() + view!!.addAllCredentials(mAccount!!.credentials) + } + + fun credentialAdded(old: AccountCredentials?, newCreds: AccountCredentials?) { + mAccount!!.addCredential(newCreds!!) + mAccountService.setCredentials(mAccount!!.accountID, mAccount!!.credentialsHashMapList) + mAccountService.setAccountDetails(mAccount!!.accountID, mAccount!!.details) + view!!.removeAllCredentials() + view!!.addAllCredentials(mAccount!!.credentials) + } + + fun tlsChanged(key: ConfigKey?, newValue: Any?) { + if (newValue is Boolean) { + mAccount!!.setDetail(key!!, (newValue as Boolean?)!!) + } else { + mAccount!!.setDetail(key!!, newValue as String?) + } + mAccountService.setCredentials(mAccount!!.accountID, mAccount!!.credentialsHashMapList) + mAccountService.setAccountDetails(mAccount!!.accountID, mAccount!!.details) + } + + fun fileActivityResult(key: ConfigKey?, filePath: String?) { + mAccount!!.setDetail(key!!, filePath) + mAccountService.setCredentials(mAccount!!.accountID, mAccount!!.credentialsHashMapList) + mAccountService.setAccountDetails(mAccount!!.accountID, mAccount!!.details) + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/SecurityAccountView.kt b/ring-android/libringclient/src/main/java/net/jami/account/SecurityAccountView.kt new file mode 100644 index 0000000000000000000000000000000000000000..931aebb4ff2a9516bdae67213f8061a13f1335c8 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/account/SecurityAccountView.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.account + +import net.jami.model.AccountConfig +import net.jami.model.AccountCredentials + +interface SecurityAccountView { + fun removeAllCredentials() + fun addAllCredentials(credentials: List<AccountCredentials>) + fun setDetails(config: AccountConfig, tlsMethods: Array<String>) +} diff --git a/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.kt index afe93d983c5562a52172d0a5f0dd8ea63e95b38a..6ae02f6b2191838b7e1aa98083c1f0fc59468f27 100644 --- a/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.kt +++ b/ring-android/libringclient/src/main/java/net/jami/call/CallPresenter.kt @@ -51,7 +51,7 @@ class CallPresenter @Inject constructor( private val mDeviceRuntimeService: DeviceRuntimeService, private val mConversationFacade: ConversationFacade, @param:Named("UiScheduler") private val mUiScheduler: Scheduler - ) : RootPresenter<CallView>() { +) : RootPresenter<CallView>() { private var mConference: Conference? = null private val mPendingCalls: MutableList<Call> = ArrayList() private val mPendingSubject: Subject<List<Call>> = BehaviorSubject.createDefault(mPendingCalls) @@ -101,12 +101,13 @@ class CallPresenter @Inject constructor( if (mSipCall != null && mSipCall.getContact() != null) { getView().updateContactBubble(mSipCall.getContact()); } - }));*/mCompositeDisposable.add(mHardwareService.getVideoEvents() + }));*/ + mCompositeDisposable.add(mHardwareService.getVideoEvents() .observeOn(mUiScheduler) .subscribe { event: VideoEvent -> onVideoEvent(event) }) mCompositeDisposable.add(mHardwareService.audioState .observeOn(mUiScheduler) - .subscribe { state: AudioState? -> getView()!!.updateAudioState(state) }) + .subscribe { state: AudioState -> this.view?.updateAudioState(state) }) /*mCompositeDisposable.add(mHardwareService .getBluetoothEvents() @@ -141,7 +142,7 @@ class CallPresenter @Inject constructor( confUpdate(conference) }) { e: Throwable -> hangupCall() - Log.e(TAG, "Error with initOutgoing: " + e.message) + Log.e(TAG, "Error with initOutgoing: " + e.message, e) }) showConference(callObservable) } @@ -171,7 +172,7 @@ class CallPresenter @Inject constructor( callInitialized = true view!!.prepareCall(true) } - }) { e: Throwable? -> + }) { e: Throwable -> hangupCall() Log.e(TAG, "Error with initIncoming, preparing call flow :", e) }) @@ -183,7 +184,7 @@ class CallPresenter @Inject constructor( contactUpdate(call) confUpdate(call) } - }) { e: Throwable? -> + }) { e: Throwable -> hangupCall() Log.e(TAG, "Error with initIncoming, action view flow: ", e) }) @@ -192,20 +193,17 @@ class CallPresenter @Inject constructor( private fun showConference(conference: Observable<Conference>) { var conference = conference - conference = conference - .distinctUntilChanged() + conference = conference.distinctUntilChanged() mCompositeDisposable.add(conference .switchMap { obj: Conference -> obj.participantInfo } .observeOn(mUiScheduler) - .subscribe( - { info: List<ParticipantInfo> -> view?.updateConfInfo(info) } - ) { e: Throwable -> Log.e(TAG, "Error with initIncoming, action view flow: ", e) }) + .subscribe({ info: List<ParticipantInfo> -> view?.updateConfInfo(info) }) + { e: Throwable -> Log.e(TAG, "Error with initIncoming, action view flow: ", e) }) mCompositeDisposable.add(conference .switchMap { obj: Conference -> obj.participantRecording } .observeOn(mUiScheduler) - .subscribe( - { contacts: Set<Contact> -> view?.updateParticipantRecording(contacts) } - ) { e: Throwable -> Log.e(TAG, "Error with initIncoming, action view flow: ", e) }) + .subscribe({ contacts: Set<Contact> -> view?.updateParticipantRecording(contacts) }) + { e: Throwable -> Log.e(TAG, "Error with initIncoming, action view flow: ", e) }) } fun prepareOptionMenu() { @@ -216,7 +214,7 @@ class CallPresenter @Inject constructor( val displayPluginsButton = view!!.displayPluginsButton() val showPluginBtn = displayPluginsButton && mOnGoingCall && mConference != null val hasMultipleCamera = mHardwareService.cameraCount > 1 && mOnGoingCall && !isAudioOnly - view!!.initMenu(isSpeakerOn, hasMultipleCamera, canDial, showPluginBtn, mOnGoingCall) + view?.initMenu(isSpeakerOn, hasMultipleCamera, canDial, showPluginBtn, mOnGoingCall) } fun chatClick() { @@ -226,10 +224,9 @@ class CallPresenter @Inject constructor( val firstCall = mConference!!.participants[0] ?: return val c = firstCall.conversation if (c is Conversation) { - val conversation = c - view!!.goToConversation(conversation.accountId, conversation.uri) + view?.goToConversation(c.accountId, c.uri) } else if (firstCall.contact != null) { - view!!.goToConversation(firstCall.account, firstCall.contact!!.conversationUri.blockingFirst()) + view?.goToConversation(firstCall.account!!, firstCall.contact!!.conversationUri.blockingFirst()) } } @@ -248,9 +245,9 @@ class CallPresenter @Inject constructor( get() = mCallService.isCaptureMuted fun switchVideoInputClick() { - if (mConference == null) return - mHardwareService.switchInput(mConference!!.id, false) - view!!.switchCameraIcon(mHardwareService.isPreviewFromFrontCamera) + val conference = mConference ?: return + mHardwareService.switchInput(conference.id, false) + view?.switchCameraIcon(mHardwareService.isPreviewFromFrontCamera) } fun configurationChanged(rotation: Int) { @@ -283,61 +280,69 @@ class CallPresenter @Inject constructor( finish() } - fun videoSurfaceCreated(holder: Any?) { + fun videoSurfaceCreated(holder: Any) { if (mConference == null) { return } val newId = mConference!!.id if (newId != currentSurfaceId) { - mHardwareService.removeVideoSurface(currentSurfaceId) + currentSurfaceId?.let { id -> + mHardwareService.removeVideoSurface(id) + } currentSurfaceId = newId } mHardwareService.addVideoSurface(mConference!!.id, holder) - view!!.displayContactBubble(false) + view?.displayContactBubble(false) } - fun videoSurfaceUpdateId(newId: String?) { + private fun videoSurfaceUpdateId(newId: String) { if (newId != currentSurfaceId) { - mHardwareService.updateVideoSurfaceId(currentSurfaceId, newId) + currentSurfaceId?.let { oldId -> + mHardwareService.updateVideoSurfaceId(oldId, newId) + } currentSurfaceId = newId } } - fun pluginSurfaceCreated(holder: Any?) { + fun pluginSurfaceCreated(holder: Any) { if (mConference == null) { return } val newId = mConference!!.pluginId if (newId != currentPluginSurfaceId) { - mHardwareService.removeVideoSurface(currentPluginSurfaceId) + currentPluginSurfaceId?.let { id -> + mHardwareService.removeVideoSurface(id) + } currentPluginSurfaceId = newId } mHardwareService.addVideoSurface(mConference!!.pluginId, holder) - view!!.displayContactBubble(false) + view?.displayContactBubble(false) } - fun pluginSurfaceUpdateId(newId: String?) { + private fun pluginSurfaceUpdateId(newId: String) { if (newId != currentPluginSurfaceId) { - mHardwareService.updateVideoSurfaceId(currentPluginSurfaceId, newId) + currentPluginSurfaceId?.let { oldId -> + mHardwareService.updateVideoSurfaceId(oldId, newId) + } currentPluginSurfaceId = newId } } - fun previewVideoSurfaceCreated(holder: Any?) { + fun previewVideoSurfaceCreated(holder: Any) { mHardwareService.addPreviewVideoSurface(holder, mConference) //mHardwareService.startCapture(null); } fun videoSurfaceDestroyed() { - if (currentSurfaceId != null) { - mHardwareService.removeVideoSurface(currentSurfaceId) + currentSurfaceId?.let { id -> + mHardwareService.removeVideoSurface(id) currentSurfaceId = null } } fun pluginSurfaceDestroyed() { - if (currentPluginSurfaceId != null) { - mHardwareService.removeVideoSurface(currentPluginSurfaceId) + currentPluginSurfaceId?.let { id -> + mHardwareService.removeVideoSurface(id) currentPluginSurfaceId = null } } @@ -357,13 +362,13 @@ class CallPresenter @Inject constructor( fun uiVisibilityChanged(displayed: Boolean) { Log.w(TAG, "uiVisibilityChanged $mOnGoingCall $displayed") - val view = view view?.displayHangupButton(mOnGoingCall && displayed) } private fun finish() { - if (timeUpdateTask != null && !timeUpdateTask!!.isDisposed) { - timeUpdateTask!!.dispose() + timeUpdateTask?.let { task -> + if (!task.isDisposed) + task.dispose() timeUpdateTask = null } mConference = null @@ -375,9 +380,7 @@ class CallPresenter @Inject constructor( private fun contactUpdate(conference: Conference) { if (mConference !== conference) { mConference = conference - if (contactDisposable != null && !contactDisposable!!.isDisposed) { - contactDisposable!!.dispose() - } + contactDisposable?.apply { dispose() } if (conference.participants.isEmpty()) return // Updates of participant (and pending participant) list @@ -392,36 +395,27 @@ class CallPresenter @Inject constructor( } // Updates of individual contacts - val contactsObservable = callsObservable - .flatMapSingle { calls: List<Call> -> - Observable.fromIterable(calls) - .map { call: Call -> mContactService.observeContact(call.account!!, call.contact!!, false) - .map { call } } - .toList(calls.size) - } + val contactsObservable = callsObservable.flatMapSingle { calls: List<Call> -> + Observable.fromIterable(calls) + .map { call: Call -> mContactService.observeContact(call.account!!, call.contact!!, false) + .map { call } } + .toList(calls.size) + } // Combined updates of contacts as participant list updates val contactUpdates = contactsObservable - .switchMap { list: List<Observable<Call>>? -> - Observable - .combineLatest(list) { objects: Array<Any> -> - Log.w(TAG, "flatMapObservable " + objects.size) - val calls = ArrayList<Call>(objects.size) - for (call in objects) calls.add(call as Call) - calls - } - } - .filter { list: List<Call> -> !list.isEmpty() } + .switchMap { list: List<Observable<Call>> -> Observable.combineLatest(list) { objects: Array<Any> -> + Log.w(TAG, "flatMapObservable " + objects.size) + val calls = ArrayList<Call>(objects.size) + for (call in objects) calls.add(call as Call) + calls + } } + .filter { list: List<Call> -> list.isNotEmpty() } contactDisposable = contactUpdates .observeOn(mUiScheduler) - .subscribe({ cs: List<Call>? -> view!!.updateContactBubble(cs) }) { e: Throwable? -> - Log.e( - TAG, - "Error updating contact data", - e - ) - } - mCompositeDisposable.add(contactDisposable) + .subscribe({ cs: List<Call> -> view?.updateContactBubble(cs) }) + { e: Throwable -> Log.e(TAG, "Error updating contact data", e) } + .apply { mCompositeDisposable.add(this) } } mPendingSubject.onNext(mPendingCalls) } @@ -450,7 +444,7 @@ class CallPresenter @Inject constructor( permissionChanged = false } } - if (timeUpdateTask != null) timeUpdateTask!!.dispose() + timeUpdateTask?.dispose() timeUpdateTask = mUiScheduler.schedulePeriodicallyDirect({ updateTime() }, 0, 1, TimeUnit.SECONDS) } else if (call.isRinging) { val scall = call.call!! @@ -500,30 +494,28 @@ class CallPresenter @Inject constructor( private fun onVideoEvent(event: VideoEvent) { Log.d(TAG, "VIDEO_EVENT: " + event.start + " " + event.callId + " " + event.w + "x" + event.h) + val view = view ?: return if (event.start) { - view!!.displayVideoSurface(true, !isPipMode && mDeviceRuntimeService.hasVideoPermission()) + view.displayVideoSurface(true, !isPipMode && mDeviceRuntimeService.hasVideoPermission()) } else if (mConference != null && mConference!!.id == event.callId) { - view!!.displayVideoSurface( - event.started, - event.started && !isPipMode && mDeviceRuntimeService.hasVideoPermission() - ) + view.displayVideoSurface(event.started, event.started && !isPipMode && mDeviceRuntimeService.hasVideoPermission()) if (event.started) { videoWidth = event.w videoHeight = event.h - view!!.resetVideoSize(videoWidth, videoHeight) + view.resetVideoSize(videoWidth, videoHeight) } } else if (event.callId == null) { if (event.started) { previewWidth = event.w previewHeight = event.h - view!!.resetPreviewVideoSize(previewWidth, previewHeight, event.rot) + view.resetPreviewVideoSize(previewWidth, previewHeight, event.rot) } } if (mConference != null && mConference!!.pluginId == event.callId) { if (event.started) { previewWidth = event.w previewHeight = event.h - view!!.resetPluginPreviewVideoSize(previewWidth, previewHeight, event.rot) + view.resetPluginPreviewVideoSize(previewWidth, previewHeight, event.rot) } } /*if (event.started || event.start) { @@ -571,9 +563,10 @@ class CallPresenter @Inject constructor( } } - fun toggleCallMediaHandler(id: String?, toggle: Boolean) { - if (mConference != null && mConference!!.isOnGoing && mConference!!.hasVideo()) { - view!!.toggleCallMediaHandler(id, mConference!!.id, toggle) + fun toggleCallMediaHandler(id: String, toggle: Boolean) { + val conference = mConference ?: return + if (conference.isOnGoing && conference.hasVideo()) { + view?.toggleCallMediaHandler(id, conference.id, toggle) } } @@ -659,8 +652,8 @@ class CallPresenter @Inject constructor( } fun openParticipantContact(info: ParticipantInfo) { - val call = info.call ?: mConference!!.firstCall!! - view!!.goToContact(call.account, info.contact) + val call = info.call ?: mConference?.firstCall ?: return + view?.goToContact(call.account!!, info.contact) } fun stopCapture() { diff --git a/ring-android/libringclient/src/main/java/net/jami/call/CallView.java b/ring-android/libringclient/src/main/java/net/jami/call/CallView.java deleted file mode 100644 index f82a975445fd1bcc7e02d17a01717298397a914b..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/call/CallView.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.call; - -import java.util.List; -import java.util.Set; - -import net.jami.model.Call; -import net.jami.model.Contact; -import net.jami.model.Conference; -import net.jami.model.Uri; -import net.jami.services.HardwareService; - -public interface CallView { - - void displayContactBubble(boolean display); - - void displayVideoSurface(boolean displayVideoSurface, boolean displayPreviewContainer); - - void displayPreviewSurface(boolean display); - - void displayHangupButton(boolean display); - - void displayDialPadKeyboard(); - - void switchCameraIcon(boolean isFront); - void updateAudioState(HardwareService.AudioState state); - - void updateMenu(); - - void updateTime(long duration); - - void updateContactBubble(List<Call> contact); - - void updateCallStatus(Call.CallStatus callState); - - void initMenu(boolean isSpeakerOn, boolean displayFlip, boolean canDial, boolean showPluginBtn, boolean onGoingCall); - - void initNormalStateDisplay(boolean audioOnly, boolean muted); - - void initIncomingCallDisplay(); - - void initOutGoingCallDisplay(); - - void resetPreviewVideoSize(int previewWidth, int previewHeight, int rot); - void resetPluginPreviewVideoSize(int previewWidth, int previewHeight, int rot); - void resetVideoSize(int videoWidth, int videoHeight); - - void goToConversation(String accountId, Uri conversationId); - - void goToAddContact(Contact contact); - - void startAddParticipant(String conferenceId); - - void finish(); - - void onUserLeave(); - - void enterPipMode(String callId); - - void prepareCall(boolean isIncoming); - - void handleCallWakelock(boolean isAudioOnly); - - void goToContact(String accountId, Contact contact); - - boolean displayPluginsButton(); - - void updateConfInfo(List<Conference.ParticipantInfo> info); - - void updateParticipantRecording(Set<Contact> contacts); - - void toggleCallMediaHandler(String id, String callId, boolean toggle); -} diff --git a/ring-android/libringclient/src/main/java/net/jami/call/CallView.kt b/ring-android/libringclient/src/main/java/net/jami/call/CallView.kt new file mode 100644 index 0000000000000000000000000000000000000000..62843bbe30c27c4414a78d24d91fef65cec59f00 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/call/CallView.kt @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.call + +import net.jami.model.Call +import net.jami.model.Call.CallStatus +import net.jami.model.Conference.ParticipantInfo +import net.jami.model.Contact +import net.jami.model.Uri +import net.jami.services.HardwareService.AudioState + +interface CallView { + fun displayContactBubble(display: Boolean) + fun displayVideoSurface(displayVideoSurface: Boolean, displayPreviewContainer: Boolean) + fun displayPreviewSurface(display: Boolean) + fun displayHangupButton(display: Boolean) + fun displayDialPadKeyboard() + fun switchCameraIcon(isFront: Boolean) + fun updateAudioState(state: AudioState) + fun updateMenu() + fun updateTime(duration: Long) + fun updateContactBubble(contact: List<Call>) + fun updateCallStatus(callState: CallStatus) + fun initMenu(isSpeakerOn: Boolean, displayFlip: Boolean, canDial: Boolean, showPluginBtn: Boolean, onGoingCall: Boolean) + fun initNormalStateDisplay(audioOnly: Boolean, muted: Boolean) + fun initIncomingCallDisplay() + fun initOutGoingCallDisplay() + fun resetPreviewVideoSize(previewWidth: Int, previewHeight: Int, rot: Int) + fun resetPluginPreviewVideoSize(previewWidth: Int, previewHeight: Int, rot: Int) + fun resetVideoSize(videoWidth: Int, videoHeight: Int) + fun goToConversation(accountId: String, conversationId: Uri) + fun goToAddContact(contact: Contact) + fun startAddParticipant(conferenceId: String) + fun finish() + fun onUserLeave() + fun enterPipMode(callId: String) + fun prepareCall(isIncoming: Boolean) + fun handleCallWakelock(isAudioOnly: Boolean) + fun goToContact(accountId: String, contact: Contact) + fun displayPluginsButton(): Boolean + fun updateConfInfo(info: List<ParticipantInfo>) + fun updateParticipantRecording(contacts: Set<Contact>) + fun toggleCallMediaHandler(id: String, callId: String, toggle: Boolean) +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/contactrequests/BlockListPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/contactrequests/BlockListPresenter.kt index 6a8b4eb6b5f4f7ebf2d2379863cad9f0f4f43876..7509b9d7ed9ad4ae0c620feb19bd9fd0d9e3c95b 100644 --- a/ring-android/libringclient/src/main/java/net/jami/contactrequests/BlockListPresenter.kt +++ b/ring-android/libringclient/src/main/java/net/jami/contactrequests/BlockListPresenter.kt @@ -35,23 +35,14 @@ class BlockListPresenter @Inject constructor( ) : RootPresenter<BlockListView>() { private var mAccountID: String? = null - override fun bindView(view: BlockListView) { - super.bindView(view) - /*if (mAccountID != null) { - setAccountId(mAccountID) - }*/ - } - private fun updateList(list: Collection<Contact>) { - if (view == null) { - return - } + val view = view ?: return if (list.isEmpty()) { - view!!.hideListView() - view!!.displayEmptyListMessage(true) + view.hideListView() + view.displayEmptyListMessage(true) } else { - view!!.updateView(list) - view!!.displayEmptyListMessage(false) + view.updateView(list) + view.displayEmptyListMessage(false) } } @@ -64,13 +55,8 @@ class BlockListPresenter @Inject constructor( .getAccountSingle(accountID) .flatMapObservable(Account::bannedContactsUpdates) .observeOn(mUiScheduler) - .subscribe({ list: Collection<Contact> -> updateList(list) }) { e: Throwable -> - Log.e( - TAG, - "Error showing blacklist", - e - ) - }) + .subscribe({ list: Collection<Contact> -> updateList(list) }) + { e: Throwable -> Log.e(TAG, "Error showing blacklist", e) }) mAccountID = accountID } diff --git a/ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsPresenter.kt index 9bc2d51375c604463438fbff4bf464ff6607ad2f..2ce7cd96533b7c62bb8f424ccc7feedcb89d4ff2 100644 --- a/ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsPresenter.kt +++ b/ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsPresenter.kt @@ -48,7 +48,7 @@ class ContactRequestsPresenter @Inject internal constructor( { ob -> ob as SmartListViewModel } } } .observeOn(mUiScheduler) - .subscribe({ viewModels -> getView()?.updateView(viewModels, mCompositeDisposable) }) + .subscribe({ viewModels -> view?.updateView(viewModels, mCompositeDisposable) }) { e: Throwable -> Log.d(TAG, "updateList subscribe onError", e) }) } @@ -67,7 +67,7 @@ class ContactRequestsPresenter @Inject internal constructor( } } - fun contactRequestClicked(accountId: String?, uri: Uri?) { + fun contactRequestClicked(accountId: String, uri: Uri) { view?.goToConversation(accountId, uri) } diff --git a/ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsView.java b/ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsView.kt similarity index 64% rename from ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsView.java rename to ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsView.kt index ca32ddac2f7a8f6663700109ce85f146d90b4350..3b1ad42dfedc6ee211f5cf71ec8b4fdd78d10843 100644 --- a/ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsView.java +++ b/ring-android/libringclient/src/main/java/net/jami/contactrequests/ContactRequestsView.kt @@ -16,20 +16,14 @@ * 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 net.jami.contactrequests -package net.jami.contactrequests; +import io.reactivex.rxjava3.disposables.CompositeDisposable +import net.jami.model.Uri +import net.jami.smartlist.SmartListViewModel -import java.util.List; - -import net.jami.model.Uri; -import net.jami.smartlist.SmartListViewModel; - -import io.reactivex.rxjava3.disposables.CompositeDisposable; - -public interface ContactRequestsView { - - void updateView(List<SmartListViewModel> list, CompositeDisposable disposable); - void updateItem(SmartListViewModel item); - - void goToConversation(String accountId, Uri contactId); -} +interface ContactRequestsView { + fun updateView(list: MutableList<SmartListViewModel>, disposable: CompositeDisposable) + fun updateItem(item: SmartListViewModel) + fun goToConversation(accountId: String, contactId: Uri) +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.kt index 1914b5b4796da5f0fafdec641d462e6decf1e7bc..9c3af235041e0b465a85c9407617fbfd024ca0ba 100644 --- a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.kt +++ b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.kt @@ -34,9 +34,8 @@ import net.jami.model.Conversation.ElementStatus import net.jami.mvp.RootPresenter import net.jami.services.* import net.jami.utils.Log -import net.jami.utils.StringUtils.isEmpty -import net.jami.utils.Tuple -import net.jami.utils.VCardUtils.vcardToString +import net.jami.utils.StringUtils +import net.jami.utils.VCardUtils import java.io.File import javax.inject.Inject import javax.inject.Named @@ -161,8 +160,7 @@ class ConversationPresenter @Inject constructor( { isConnected: Boolean, a: Account -> isConnected || a.isRegistered }) .observeOn(mUiScheduler) .subscribe { isOk: Boolean -> - val v = getView() - if (v != null) { + this.view?.let { v -> if (!isOk) v.displayNetworkErrorPanel() else if (!account.isEnabled) { v.displayAccountOfflineErrorPanel() } else { @@ -172,12 +170,12 @@ class ConversationPresenter @Inject constructor( }) disposable.add(c.sortedHistory .observeOn(mUiScheduler) - .subscribe({ conversation: List<Interaction> -> view.refreshView(conversation) }) { e: Throwable -> + .subscribe({ conversation: List<Interaction> -> this.view?.refreshView(conversation) }) { e: Throwable -> Log.e(TAG, "Can't update element", e) }) disposable.add(c.cleared .observeOn(mUiScheduler) - .subscribe({ conversation: List<Interaction> -> view.refreshView(conversation) }) { e: Throwable -> + .subscribe({ conversation: List<Interaction> -> this.view?.refreshView(conversation) }) { e: Throwable -> Log.e(TAG, "Can't update elements", e) }) disposable.add(c.contactUpdates @@ -185,25 +183,26 @@ class ConversationPresenter @Inject constructor( Observable.merge(mContactService.observeLoadedContact(c.accountId, contacts, true)) } .observeOn(mUiScheduler) - .subscribe { contact: Contact -> getView()?.updateContact(contact) }) + .subscribe { contact: Contact -> this.view?.updateContact(contact) }) disposable.add(c.updatedElements .observeOn(mUiScheduler) .subscribe({ elementTuple -> + val v = this.view ?: return@subscribe when (elementTuple.second) { - ElementStatus.ADD -> view.addElement(elementTuple.first) - ElementStatus.UPDATE -> view.updateElement(elementTuple.first) - ElementStatus.REMOVE -> view.removeElement(elementTuple.first) + ElementStatus.ADD -> v.addElement(elementTuple.first) + ElementStatus.UPDATE -> v.updateElement(elementTuple.first) + ElementStatus.REMOVE -> v.removeElement(elementTuple.first) } }, { e: Throwable -> Log.e(TAG, "Can't update element", e) }) ) if (showTypingIndicator()) { disposable.add(c.composingStatus .observeOn(mUiScheduler) - .subscribe { composingStatus: ComposingStatus -> view.setComposingStatus(composingStatus) }) + .subscribe { composingStatus: ComposingStatus -> this.view?.setComposingStatus(composingStatus) }) } disposable.add(c.getLastDisplayed() .observeOn(mUiScheduler) - .subscribe { interaction: Interaction -> view.setLastDisplayed(interaction) }) + .subscribe { interaction: Interaction -> this.view?.setLastDisplayed(interaction) }) disposable.add(c.calls .observeOn(mUiScheduler) .subscribe({ updateOngoingCallView(c) }) { e: Throwable -> @@ -224,7 +223,7 @@ class ConversationPresenter @Inject constructor( .observeOn(mUiScheduler) .subscribe { Log.e(TAG, "getLocationUpdates: update") - getView()?.showMap(c.accountId, c.uri.uri, false) + view?.showMap(c.accountId, c.uri.uri, false) } ) } @@ -239,7 +238,7 @@ class ConversationPresenter @Inject constructor( fun sendTextMessage(message: String?) { val conversation = mConversation - if (isEmpty(message) || conversation == null) { + if (message == null || message.isEmpty() || conversation == null) { return } val conference = conversation.currentCall @@ -306,7 +305,7 @@ class ConversationPresenter @Inject constructor( contact.status = Contact.Status.REQUEST_SENT mVCardService.loadSmallVCardWithDefault(conversation.accountId, VCardService.MAX_SIZE_REQUEST) .subscribeOn(Schedulers.computation()) - .subscribe({ vCard -> mAccountService.sendTrustRequest(conversation, contact.uri, Blob.fromString(vcardToString(vCard)))}) + .subscribe({ vCard -> mAccountService.sendTrustRequest(conversation, contact.uri, Blob.fromString(VCardUtils.vcardToString(vCard)))}) { mAccountService.sendTrustRequest(conversation, contact.uri, null) } } @@ -405,8 +404,8 @@ class ConversationPresenter @Inject constructor( view?.showPluginListHandlers(mConversation!!.accountId, mConversationUri!!.uri) } - val path: Tuple<String, String> - get() = Tuple(mConversation!!.accountId, mConversationUri!!.uri) + val path: Pair<String, String> + get() = Pair(mConversation!!.accountId, mConversationUri!!.uri) fun onComposingChanged(hasMessage: Boolean) { if (showTypingIndicator()) { diff --git a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.kt b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.kt index d69902d1e8790e7cd690a73beffaa05c6438a0e5..5d6b2ffbb17cde6729103e75177a182f1516e80d 100644 --- a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.kt +++ b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.kt @@ -21,17 +21,16 @@ package net.jami.conversation import net.jami.model.* import net.jami.model.Account.ComposingStatus -import net.jami.mvp.BaseView import java.io.File -interface ConversationView : BaseView { +interface ConversationView { fun refreshView(conversation: List<Interaction>) fun scrollToEnd() fun updateContact(contact: Contact) fun displayContact(conversation: Conversation) fun displayOnGoingCallPane(display: Boolean) fun displayNumberSpinner(conversation: Conversation, number: Uri) - override fun displayErrorToast(error: Error) + fun displayErrorToast(error: Error) fun hideNumberSpinner() fun clearMsgEdit() fun goToHome() diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Account.kt b/ring-android/libringclient/src/main/java/net/jami/model/Account.kt index 5b32d3296d5a236fce941e30a34955097515c06b..05e2d20ad5c1b1aaaa2b201e4cab6f7320cf2d2d 100644 --- a/ring-android/libringclient/src/main/java/net/jami/model/Account.kt +++ b/ring-android/libringclient/src/main/java/net/jami/model/Account.kt @@ -30,17 +30,15 @@ import net.jami.services.AccountService import net.jami.smartlist.SmartListViewModel import net.jami.utils.Log import net.jami.utils.StringUtils -import net.jami.utils.Tuple import java.lang.IllegalStateException import java.util.* class Account( - bAccountID: String, + val accountID: String, details: Map<String, String>, credentials: List<Map<String, String>>, volDetails: Map<String, String> ) { - val accountID: String = bAccountID private var mVolatileDetails: AccountConfig var config: AccountConfig private set @@ -75,14 +73,15 @@ class Account( private val mLocationStartedSubject: Subject<ContactLocationEntry> = PublishSubject.create() var historyLoader: Single<Account>? = null - var loadedProfile: Single<Tuple<String?, Any?>>? = null + var loadedProfile: Single<Profile>? = null set(profile) { field = profile - mProfileSubject.onNext(profile) + if (profile != null) + mProfileSubject.onNext(profile) } - private val mProfileSubject: Subject<Single<Tuple<String?, Any?>>> = BehaviorSubject.create() - val loadedProfileObservable: Observable<Tuple<String?, Any?>> = mProfileSubject.switchMapSingle { single -> single } + private val mProfileSubject: Subject<Single<Profile>> = BehaviorSubject.create() + val loadedProfileObservable: Observable<Profile> = mProfileSubject.switchMapSingle { single -> single } fun cleanup() { conversationSubject.onComplete() @@ -307,8 +306,8 @@ class Account( pendingChanged() } - fun updated(conversation: Conversation?) { - val key = conversation!!.uri.uri + fun updated(conversation: Conversation) { + val key = conversation.uri.uri synchronized(conversations) { if (conversation == conversations[key]) { conversationUpdated(conversation) @@ -358,7 +357,7 @@ class Account( conversation = getConversationByCallId(daemonId) } if (conversation == null) { - conversation = getByKey(txt.conversation!!.participant) + conversation = getByKey(txt.conversation!!.participant!!) txt.contact = conversation.contact } conversation.addTextMessage(txt) @@ -421,7 +420,7 @@ class Account( username = config[ConfigKey.ACCOUNT_USERNAME] } - fun setDetail(key: ConfigKey, value: String) { + fun setDetail(key: ConfigKey, value: String?) { config.put(key, value) } @@ -544,9 +543,7 @@ class Account( val credentialsHashMapList: List<Map<String, String>> get() { - val result = ArrayList<Map<String, String>>( - credentials.size - ) + val result = ArrayList<Map<String, String>>(credentials.size) for (cred in credentials) { result.add(cred.details) } @@ -947,7 +944,9 @@ class Account( val accountAlias: Single<String> get() = loadedProfileObservable.firstOrError() - .map { p: Tuple<String?, Any?> -> if (StringUtils.isEmpty(p.first)) if (isJami) jamiAlias else alias else p.first } + .map { p -> if (p.displayName == null || p.displayName.isEmpty()) + if (isJami) jamiAlias else alias!! + else p.displayName } /** * Registered name, fallback to Alias @@ -958,10 +957,6 @@ class Account( return if (StringUtils.isEmpty(registeredName)) alias!! else registeredName } - fun resetProfile() { - loadedProfile = null - } - fun getDataTransfer(id: String): DataTransfer? { return mDataTransfers[id] } diff --git a/ring-android/libringclient/src/main/java/net/jami/model/AccountConfig.kt b/ring-android/libringclient/src/main/java/net/jami/model/AccountConfig.kt index 1ed759a18a1dd8e4055ddc378ba16c4fe107fcc6..104af34d0fa2b005346bd34103c9207f58d60045 100644 --- a/ring-android/libringclient/src/main/java/net/jami/model/AccountConfig.kt +++ b/ring-android/libringclient/src/main/java/net/jami/model/AccountConfig.kt @@ -40,8 +40,11 @@ class AccountConfig(details: Map<String, String>) { return details } - fun put(key: ConfigKey, value: String) { - mValues[key] = value + fun put(key: ConfigKey, value: String?) { + if (value == null) + mValues.remove(key) + else + mValues[key] = value } fun put(key: ConfigKey, value: Boolean) { diff --git a/ring-android/libringclient/src/main/java/net/jami/model/AccountCreationModel.kt b/ring-android/libringclient/src/main/java/net/jami/model/AccountCreationModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..4273d39568c5e82f2def56770349170b9475102b --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/model/AccountCreationModel.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.model + +import ezvcard.VCard +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.subjects.BehaviorSubject +import io.reactivex.rxjava3.subjects.Subject +import java.io.File +import java.io.Serializable + +abstract class AccountCreationModel : Serializable { + var managementServer: String? = null + private var mFullName = "" + var username = "" + var password = "" + private var mPin = "" + var archive: File? = null + var isLink = false + var isPush = true + + @Transient + var newAccount: Account? = null + set(account) { + field = account + profile.onNext(this) + } + + @Transient + open var photo: Any? = null + set(photo) { + field = photo + profile.onNext(this) + } + + @Transient + var accountObservable: Observable<Account>? = null + + @Transient + protected val profile: Subject<AccountCreationModel> = BehaviorSubject.createDefault(this) + + var fullName: String + get() = mFullName + set(fullName) { + mFullName = fullName + profile.onNext(this) + } + var pin: String + get() = mPin + set(pin) { + mPin = pin.toUpperCase() + } + + abstract fun toVCard(): Single<VCard> + + val profileUpdates: Observable<AccountCreationModel> + get() = profile +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/model/AccountCredentials.java b/ring-android/libringclient/src/main/java/net/jami/model/AccountCredentials.java deleted file mode 100644 index c160d84b07e89c0b080168cbc6eeb0b04206b843..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/model/AccountCredentials.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2004-2016 Savoir-faire Linux Inc. - * <p> - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * Adrien Béraud <adrien.beraud@savoirfairelinux.com> - * <p> - * 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. - * <p> - * 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. - * <p> - * 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 net.jami.model; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.Map; - -public class AccountCredentials implements Serializable { - - @SuppressWarnings("unused") - private static final String TAG = AccountCredentials.class.getSimpleName(); - - private String mUsername; - private String mPassword; - private String mRealm; - - public AccountCredentials(Map<String, String> pref) { - mUsername = pref.get(ConfigKey.ACCOUNT_USERNAME.key()); - mPassword = pref.get(ConfigKey.ACCOUNT_PASSWORD.key()); - mRealm = pref.get(ConfigKey.ACCOUNT_REALM.key()); - } - - public AccountCredentials(String username, String password, String realm) { - setUsername(username); - setPassword(password); - setRealm(realm); - } - - public void setUsername(String val) { - mUsername = val; - } - - public void setPassword(String val) { - mPassword = val; - } - - public void setRealm(String val) { - mRealm = val; - } - - public String getUsername() { - return mUsername; - } - - public String getPassword() { - return mPassword; - } - - public String getRealm() { - return mRealm; - } - - public HashMap<String, String> getDetails() { - HashMap<String, String> details = new HashMap<>(); - details.put(ConfigKey.ACCOUNT_USERNAME.key(), mUsername); - details.put(ConfigKey.ACCOUNT_PASSWORD.key(), mPassword); - details.put(ConfigKey.ACCOUNT_REALM.key(), mRealm); - return details; - } - - public void setDetail(ConfigKey key, String value) { - - switch (key) { - case ACCOUNT_USERNAME: - mUsername = value; - break; - case ACCOUNT_PASSWORD: - mPassword = value; - break; - case ACCOUNT_REALM: - mRealm = value; - break; - } - } -} diff --git a/ring-android/libringclient/src/main/java/net/jami/model/AccountCredentials.kt b/ring-android/libringclient/src/main/java/net/jami/model/AccountCredentials.kt new file mode 100644 index 0000000000000000000000000000000000000000..2a827a5c4a53c4b087c2830bea8e669aa2944d9e --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/model/AccountCredentials.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2004-2016 Savoir-faire Linux Inc. + * <p> + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * <p> + * 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. + * <p> + * 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. + * <p> + * 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 net.jami.model + +import java.io.Serializable +import java.util.HashMap + +class AccountCredentials : Serializable { + var username: String? = null + var password: String? = null + var realm: String? = null + + constructor(pref: Map<String, String>) { + username = pref[ConfigKey.ACCOUNT_USERNAME.key()] + password = pref[ConfigKey.ACCOUNT_PASSWORD.key()] + realm = pref[ConfigKey.ACCOUNT_REALM.key()] + } + + constructor(username: String?, password: String?, realm: String?) { + this.username = username + this.password = password + this.realm = realm + } + + val details: HashMap<String, String> + get() { + val details = HashMap<String, String>() + details[ConfigKey.ACCOUNT_USERNAME.key()] = username ?: "" + details[ConfigKey.ACCOUNT_PASSWORD.key()] = password ?: "" + details[ConfigKey.ACCOUNT_REALM.key()] = realm ?: "" + return details + } + + fun setDetail(key: ConfigKey, value: String?) { + when (key) { + ConfigKey.ACCOUNT_USERNAME -> username = value + ConfigKey.ACCOUNT_PASSWORD -> password = value + ConfigKey.ACCOUNT_REALM -> realm = value + } + } + + companion object { + private val TAG = AccountCredentials::class.java.simpleName + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Codec.java b/ring-android/libringclient/src/main/java/net/jami/model/Codec.java deleted file mode 100644 index 284ecad5550e58facc864ec59dcf5cf221f79af9..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/model/Codec.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -package net.jami.model; - -import java.util.Map; - -public class Codec { - - public enum Type {AUDIO, VIDEO} - - private long mPayload; - private String mName; - private Type mType; - private String mSampleRate; - private String mBitRate; - private String mChannels; - private boolean mIsEnabled; - - public Codec(long i, Map<String, String> audioCodecDetails, boolean enabled) { - mPayload = i; - mName = audioCodecDetails.get("CodecInfo.name"); - mType = audioCodecDetails.get("CodecInfo.type").contentEquals("AUDIO") ? Type.AUDIO : Type.VIDEO; - if (audioCodecDetails.containsKey("CodecInfo.sampleRate")) { - mSampleRate = audioCodecDetails.get("CodecInfo.sampleRate"); - } - if (audioCodecDetails.containsKey("CodecInfo.bitrate")) { - mBitRate = audioCodecDetails.get("CodecInfo.bitrate"); - } - if (audioCodecDetails.containsKey("CodecInfo.channelNumber")) { - mChannels = audioCodecDetails.get("CodecInfo.channelNumber"); - } - mIsEnabled = enabled; - } - - @Override - public String toString() { - return "Codec: " + getName() - + "\n" + "Payload: " + getPayload() - + "\n" + "Sample Rate: " + getSampleRate() - + "\n" + "Bit Rate: " + getBitRate() - + "\n" + "Channels: " + getChannels(); - } - - public Type getType() { - return mType; - } - - public Long getPayload() { - return mPayload; - } - - public CharSequence getName() { - return mName; - } - - public String getSampleRate() { - return mSampleRate; - } - - public String getBitRate() { - return mBitRate; - } - - public String getChannels() { - return mChannels; - } - - public boolean isEnabled() { - return mIsEnabled; - } - - public void setEnabled(boolean enabled) { - mIsEnabled = enabled; - } - - public void toggleState() { - mIsEnabled = !mIsEnabled; - } - - @Override - public boolean equals(Object o) { - return o instanceof Codec && ((Codec) o).mPayload == mPayload; - } - - public boolean isSpeex() { - return mName.contentEquals("speex"); - } - -} diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Codec.kt b/ring-android/libringclient/src/main/java/net/jami/model/Codec.kt new file mode 100644 index 0000000000000000000000000000000000000000..86355da3ccc8ffe423d647a6fd35afde25f9e075 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/model/Codec.kt @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.model + +class Codec(val payload: Long, audioCodecDetails: Map<String, String>, enabled: Boolean) { + enum class Type { + AUDIO, VIDEO + } + + private val mName: String? = audioCodecDetails["CodecInfo.name"] + val type: Type = if (audioCodecDetails["CodecInfo.type"].contentEquals("AUDIO")) Type.AUDIO else Type.VIDEO + var sampleRate: String? = audioCodecDetails["CodecInfo.sampleRate"] + var bitRate: String? = audioCodecDetails["CodecInfo.bitrate"] + var channels: String? = audioCodecDetails["CodecInfo.channelNumber"] + var isEnabled: Boolean = enabled + override fun toString(): String { + return """ + Codec: $name + Payload: $payload + Sample Rate: $sampleRate + Bit Rate: $bitRate + Channels: $channels + """.trimIndent() + } + + val name: CharSequence? + get() = mName + + fun toggleState() { + isEnabled = !isEnabled + } + + override fun equals(other: Any?): Boolean { + return other is Codec && other.payload == payload + } + + val isSpeex: Boolean + get() = mName.contentEquals("speex") +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Contact.kt b/ring-android/libringclient/src/main/java/net/jami/model/Contact.kt index dcaf93995788564a42b25e65fa0bddfe4de94503..549139497ac23c18de85e742aa7b539a556b8d8c 100644 --- a/ring-android/libringclient/src/main/java/net/jami/model/Contact.kt +++ b/ring-android/libringclient/src/main/java/net/jami/model/Contact.kt @@ -53,7 +53,6 @@ class Contact private constructor( private var mLookupKey: String? = null var isUsernameLoaded = false private set - @JvmField var detailsLoaded = false private val mConversationUri: BehaviorSubject<Uri> = BehaviorSubject.createDefault(uri) var photo: Any? = null @@ -62,7 +61,6 @@ class Contact private constructor( var presenceUpdates: Observable<Boolean>? = null private var mContactPresenceEmitter: Emitter<Boolean>? = null - @JvmOverloads constructor(uri: Uri, user: Boolean = false) : this(uri, null, user) { } @@ -156,11 +154,11 @@ class Contact private constructor( if (!hasNumber(tel)) phones.add(Phone(tel, cat, label)) } - fun addNumber(tel: String, cat: Int, label: String?, type: Phone.NumberType?) { + fun addNumber(tel: String, cat: Int, label: String?, type: Phone.NumberType) { if (!hasNumber(tel)) phones.add(Phone(tel, cat, label, type)) } - fun addNumber(tel: Uri, cat: Int, label: String?, type: Phone.NumberType?) { + fun addNumber(tel: Uri, cat: Int, label: String?, type: Phone.NumberType) { if (!hasNumber(tel)) phones.add(Phone(tel, cat, label, type)) } @@ -199,6 +197,10 @@ class Contact private constructor( } return false } + fun setProfile(profile: Profile?) { + if (profile == null) return + setProfile(profile.displayName, profile.avatar) + } fun setProfile(name: String?, photo: Any?) { if (name != null && name.isNotEmpty() && !name.startsWith(Uri.RING_URI_SCHEME)) { @@ -217,15 +219,12 @@ class Contact private constructor( const val DEFAULT_ID = 0 const val PREFIX_RING = Uri.RING_URI_SCHEME - @JvmStatic fun buildSIP(to: Uri): Contact { val contact = Contact(to) contact.isUsernameLoaded = true return contact } - @JvmStatic - @JvmOverloads fun build(uri: String, isUser: Boolean = false): Contact { return Contact(Uri.fromString(uri), isUser) } diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Conversation.kt b/ring-android/libringclient/src/main/java/net/jami/model/Conversation.kt index 6ebbbc7a2bd5acaaa0f71afce9be20051f212179..abd8ab682fea5ab3de17a8daa7a5c2de14fb24ec 100644 --- a/ring-android/libringclient/src/main/java/net/jami/model/Conversation.kt +++ b/ring-android/libringclient/src/main/java/net/jami/model/Conversation.kt @@ -70,7 +70,7 @@ class Conversation : ConversationHistory { this.accountId = accountId contacts = mutableListOf(contact) uri = contact.uri - mParticipant = contact.uri.uri + participant = contact.uri.uri mContactSubject.onNext(contacts) mMode = BehaviorSubject.createDefault(Mode.Legacy) } @@ -640,8 +640,8 @@ class Conversation : ConversationHistory { } interface ConversationActionCallback { - fun removeConversation(callContact: Uri) - fun clearConversation(callContact: Uri) + fun removeConversation(conversationUri: Uri) + fun clearConversation(conversationUri: Uri) fun copyContactNumberToClipboard(contactNumber: String) } diff --git a/ring-android/libringclient/src/main/java/net/jami/model/ConversationHistory.java b/ring-android/libringclient/src/main/java/net/jami/model/ConversationHistory.java deleted file mode 100644 index 4253dc76d328179aec7255d1da1ac229fd198942..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/model/ConversationHistory.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Rayan Osseiran <rayan.osseiran@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.model; - -import com.j256.ormlite.field.DatabaseField; -import com.j256.ormlite.table.DatabaseTable; - -@DatabaseTable(tableName = ConversationHistory.TABLE_NAME) -public class ConversationHistory { - - public static final String TABLE_NAME = "conversations"; - public static final String COLUMN_CONVERSATION_ID = "id"; - public static final String COLUMN_PARTICIPANT = "participant"; - public static final String COLUMN_EXTRA_DATA = "extra_data"; - - @DatabaseField(generatedId = true , columnName = COLUMN_CONVERSATION_ID, canBeNull = false) - Integer mId; - @DatabaseField(columnName = COLUMN_PARTICIPANT, index = true) - String mParticipant; - @DatabaseField(columnName = COLUMN_EXTRA_DATA) - String mExtraData; - - /* Needed by ORMLite */ - public ConversationHistory() { - } - - public ConversationHistory(Conversation conversation) { - mId = conversation.getId(); - mParticipant = conversation.getParticipant(); - } - - public ConversationHistory(Integer id, String participant) { - mId = id; - mParticipant = participant; - } - - public ConversationHistory(String participant) { - mParticipant = participant; - } - - public Integer getId() { - return mId; - } - - public void setId(Integer id) { - mId = id; - } - - public String getParticipant() { - return mParticipant; - } - -} diff --git a/ring-android/libringclient/src/main/java/net/jami/model/ConversationHistory.kt b/ring-android/libringclient/src/main/java/net/jami/model/ConversationHistory.kt new file mode 100644 index 0000000000000000000000000000000000000000..e1c215578c4e9b1348437ebdfacc000f195c6cd7 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/model/ConversationHistory.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Rayan Osseiran <rayan.osseiran@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.model + +import com.j256.ormlite.table.DatabaseTable +import com.j256.ormlite.field.DatabaseField + +@DatabaseTable(tableName = ConversationHistory.TABLE_NAME) +open class ConversationHistory { + @DatabaseField(generatedId = true, columnName = COLUMN_CONVERSATION_ID, canBeNull = false) + var id: Int? = null + + @DatabaseField(columnName = COLUMN_PARTICIPANT, index = true) + var participant: String? = null + + @DatabaseField(columnName = COLUMN_EXTRA_DATA) + var mExtraData: String? = null + + /* Needed by ORMLite */ + constructor() + constructor(conversation: Conversation) { + id = conversation.id + participant = conversation.participant + } + + constructor(id: Int, participant: String) { + this.id = id + this.participant = participant + } + + constructor(participant: String) { + this.participant = participant + } + + companion object { + const val TABLE_NAME = "conversations" + const val COLUMN_CONVERSATION_ID = "id" + const val COLUMN_PARTICIPANT = "participant" + const val COLUMN_EXTRA_DATA = "extra_data" + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/model/DataTransferError.java b/ring-android/libringclient/src/main/java/net/jami/model/DataTransferError.kt similarity index 88% rename from ring-android/libringclient/src/main/java/net/jami/model/DataTransferError.java rename to ring-android/libringclient/src/main/java/net/jami/model/DataTransferError.kt index c8c929f35bedb1612a352d7fbf7dced1e1673788..1a1e830793a067c0a0fa40c5da05a1dfa6aa9eac 100644 --- a/ring-android/libringclient/src/main/java/net/jami/model/DataTransferError.java +++ b/ring-android/libringclient/src/main/java/net/jami/model/DataTransferError.kt @@ -17,12 +17,8 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +package net.jami.model -package net.jami.model; - -public enum DataTransferError { - SUCCESS, - UNKNOWN, - IO, - INVALID_ARGUMENT +enum class DataTransferError { + SUCCESS, UNKNOWN, IO, INVALID_ARGUMENT } \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/mvp/BaseView.java b/ring-android/libringclient/src/main/java/net/jami/model/Error.kt similarity index 85% rename from ring-android/libringclient/src/main/java/net/jami/mvp/BaseView.java rename to ring-android/libringclient/src/main/java/net/jami/model/Error.kt index 93faed58eb5a9f1c97dc4747fe537a12d5471ec1..bf0f77db283a93ea7b5e3db0e40b68977af69ea4 100644 --- a/ring-android/libringclient/src/main/java/net/jami/mvp/BaseView.java +++ b/ring-android/libringclient/src/main/java/net/jami/model/Error.kt @@ -1,3 +1,5 @@ +package net.jami.model + /* * Copyright (C) 2004-2021 Savoir-faire Linux Inc. * @@ -17,12 +19,6 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -package net.jami.mvp; - -import net.jami.model.Error; - -public interface BaseView { - - void displayErrorToast(Error error); - -} +enum class Error { + GENERIC_ERROR, NO_MICROPHONE, NO_INPUT, INVALID_FILE, NOT_ABLE_TO_WRITE_FILE, NO_SPACE_LEFT +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Phone.java b/ring-android/libringclient/src/main/java/net/jami/model/Phone.java deleted file mode 100644 index 355fcc525a3b5d4db393e715127f01ddeedd6f9a..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/model/Phone.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.model; - -public class Phone { - - // TODO: make sure this is usefull since a Uri should already know this - private NumberType mNumberType; - - private Uri mNumber; - private int mCategory; // Home, work, custom etc. - private String mLabel; - - public Phone(Uri number, int category) { - mNumberType = NumberType.UNKNOWN; - mNumber = number; - mLabel = null; - mCategory = category; - } - - public Phone(String number, int category) { - this(number, category, null); - } - - public Phone(String number, int category, String label) { - mNumberType = NumberType.UNKNOWN; - mCategory = category; - mNumber = Uri.fromString(number); - mLabel = label; - } - public Phone(Uri number, int category, String label) { - mNumberType = NumberType.UNKNOWN; - mCategory = category; - mNumber = number; - mLabel = label; - } - - public Phone(String number, int category, String label, NumberType numberType) { - mNumberType = numberType; - mNumber = Uri.fromString(number); - mLabel = label; - mCategory = category; - } - public Phone(Uri number, int category, String label, NumberType numberType) { - mNumberType = numberType; - mNumber = number; - mLabel = label; - mCategory = category; - } - - public NumberType getType() { - return getNumbertype(); - } - - public void setType(int type) { - setNumberType(NumberType.fromInteger(type)); - } - - public Uri getNumber() { - return mNumber; - } - - public void setNumber(String number) { - setNumber(Uri.fromString(number)); - } - - public NumberType getNumbertype() { - return mNumberType; - } - - public void setNumber(Uri number) { - this.mNumber = number; - } - - public int getCategory() { - return mCategory; - } - - public String getLabel() { - return mLabel; - } - - public void setNumberType(NumberType numberType) { - this.mNumberType = numberType; - } - - public enum NumberType { - UNKNOWN(0), - TEL(1), - SIP(2), - IP(2), - RING(3); - - private final int type; - - NumberType(int t) { - type = t; - } - - private static final NumberType[] VALS = NumberType.values(); - - public static NumberType fromInteger(int id) { - for (NumberType type : VALS) { - if (type.type == id) { - return type; - } - } - return UNKNOWN; - } - } -} diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Phone.kt b/ring-android/libringclient/src/main/java/net/jami/model/Phone.kt new file mode 100644 index 0000000000000000000000000000000000000000..9153cbbe14e7467edd59108a9106b89862dc8e9c --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/model/Phone.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.model + +import net.jami.model.Uri.Companion.fromString +import kotlin.jvm.JvmOverloads + +class Phone { + val numbertype: NumberType + val number: Uri + // Home, work, custom etc. + val category : Int + val label: String? + + constructor(number: Uri, category: Int) { + numbertype = NumberType.UNKNOWN + this.number = number + label = null + this.category = category + } + + @JvmOverloads + constructor(number: String?, category: Int, label: String? = null) { + numbertype = NumberType.UNKNOWN + this.category = category + this.number = fromString(number!!) + this.label = label + } + + constructor(number: Uri, category: Int, label: String?) { + numbertype = NumberType.UNKNOWN + this.category = category + this.number = number + this.label = label + } + + constructor(number: String?, category: Int, label: String?, numberType: NumberType) { + numbertype = numberType + this.number = fromString(number!!) + this.label = label + this.category = category + } + + constructor(number: Uri, category: Int, label: String?, numberType: NumberType) { + numbertype = numberType + this.number = number + this.label = label + this.category = category + } + + val type: NumberType + get() = numbertype + + enum class NumberType(private val type: Int) { + UNKNOWN(0), TEL(1), SIP(2), IP(2), RING(3); + + companion object { + private val VALS = values() + fun fromInteger(id: Int): NumberType { + for (type in VALS) { + if (type.type == id) { + return type + } + } + return UNKNOWN + } + } + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Profile.kt b/ring-android/libringclient/src/main/java/net/jami/model/Profile.kt new file mode 100644 index 0000000000000000000000000000000000000000..ec003b3cb8d3282cbd2f70a69ccae12f78274b0d --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/model/Profile.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * Author: Raphaël Brulé <raphael.brule@savoirfairelinux.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 <https://www.gnu.org/licenses/>. + */ +package net.jami.model + +class Profile(val displayName: String?, val avatar: Any?) diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Ringtone.java b/ring-android/libringclient/src/main/java/net/jami/model/Ringtone.java deleted file mode 100644 index 19a31f1f964d46ea4928d0d96a7c43e3dae795d6..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/model/Ringtone.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Authors: Rayan Osseiran <rayan.osseiran@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.model; - -public class Ringtone { - - - private String mName, mRingtonePath; - private boolean isSelected, isPlaying; - private Object mRingtoneIcon; - - - public Ringtone(String name, String path, Object ringtoneIcon) { - mName = name; - mRingtonePath = path; - isSelected = false; - isPlaying = false; - mRingtoneIcon = ringtoneIcon; - } - - public boolean isSelected() { - return isSelected; - } - - public void setSelected(boolean selected) { - isSelected = selected; - } - - public boolean isPlaying() { - return isPlaying; - } - - public void setPlaying(boolean playing) { - isPlaying = playing; - } - - public String getName() { - return mName; - } - - public void setName(String name) { - mName = name; - } - - public String getRingtonePath() { - return mRingtonePath; - } - - public void setRingtonePath(String ringtonePath) { - mRingtonePath = ringtonePath; - } - - public Object getRingtoneIcon() { - return mRingtoneIcon; - } - - public void setRingtoneIcon(Object ringtoneIcon) { - mRingtoneIcon = ringtoneIcon; - } - - -} diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Ringtone.kt b/ring-android/libringclient/src/main/java/net/jami/model/Ringtone.kt new file mode 100644 index 0000000000000000000000000000000000000000..2d6e07a4e137801944155582d46dc97b82db67c8 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/model/Ringtone.kt @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Authors: Rayan Osseiran <rayan.osseiran@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.model + +class Ringtone(val name: String, val ringtonePath: String?, val ringtoneIcon: Any) { + var isSelected = false + var isPlaying = false +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Settings.java b/ring-android/libringclient/src/main/java/net/jami/model/Settings.java deleted file mode 100644 index ad97f2a6617094d15093f4b886cff04264c2fa0f..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/model/Settings.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.model; - -public class Settings { - private boolean mAllowPushNotifications; - private boolean mAllowPersistentNotification; - private boolean mAllowSystemContacts; - private boolean mAllowPlaceSystemCalls; - private boolean mAllowOnStartup; - private boolean mAllowTypingIndicator; - private boolean mAllowReadIndicator; - private boolean mBlockRecordIndicator; - private boolean mHwEncoding; - private int mNotificationVisibility; - - public Settings() { - } - - public Settings(Settings s) { - if (s != null) { - mAllowPushNotifications = s.mAllowPushNotifications; - mAllowPersistentNotification = s.mAllowPersistentNotification; - mAllowSystemContacts = s.mAllowSystemContacts; - mAllowPlaceSystemCalls = s.mAllowPlaceSystemCalls; - mAllowOnStartup = s.mAllowOnStartup; - mAllowTypingIndicator = s.mAllowTypingIndicator; - mAllowReadIndicator = s.mAllowReadIndicator; - mBlockRecordIndicator = s.mBlockRecordIndicator; - mHwEncoding = s.mHwEncoding; - mNotificationVisibility = s.mNotificationVisibility; - } - } - - public boolean isAllowPushNotifications() { - return mAllowPushNotifications; - } - - public void setAllowPushNotifications(boolean push) { - this.mAllowPushNotifications = push; - } - - public boolean isAllowSystemContacts() { - return mAllowSystemContacts; - } - - public void setAllowSystemContacts(boolean allowSystemContacts) { - this.mAllowSystemContacts = allowSystemContacts; - } - - public boolean isAllowPlaceSystemCalls() { - return mAllowPlaceSystemCalls; - } - - public void setAllowPlaceSystemCalls(boolean allowPlaceSystemCalls) { - this.mAllowPlaceSystemCalls = allowPlaceSystemCalls; - } - - public boolean isAllowOnStartup() { - return mAllowOnStartup; - } - - public void setAllowRingOnStartup(boolean allowRingOnStartup) { - this.mAllowOnStartup = allowRingOnStartup; - } - - public void setAllowPersistentNotification(boolean checked) { - this.mAllowPersistentNotification = checked; - } - - public boolean isAllowPersistentNotification() { - return mAllowPersistentNotification; - } - - public void setAllowTypingIndicator(boolean checked) { - this.mAllowTypingIndicator = checked; - } - - public boolean isAllowTypingIndicator() { - return mAllowTypingIndicator; - } - - public void setAllowReadIndicator(boolean checked) { - this.mAllowReadIndicator = checked; - } - - public boolean isAllowReadIndicator() { - return mAllowReadIndicator; - } - - public void setBlockRecordIndicator(boolean checked) { - this.mBlockRecordIndicator = checked; - } - - public boolean isRecordingBlocked() { - return mBlockRecordIndicator; - } - - public void setNotificationVisibility(int visibility) { - this.mNotificationVisibility = visibility; - } - - public int getNotificationVisibility() { - return mNotificationVisibility; - } - -} diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Settings.kt b/ring-android/libringclient/src/main/java/net/jami/model/Settings.kt new file mode 100644 index 0000000000000000000000000000000000000000..e3e84fbc2e529a7f084fd73199d648991efb63d1 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/model/Settings.kt @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.model + +class Settings(s: Settings? = null) { + var isAllowPushNotifications = false + var isAllowPersistentNotification = false + var isAllowSystemContacts = false + var isAllowPlaceSystemCalls = false + var isAllowOnStartup = false + private set + var isAllowTypingIndicator = false + var isAllowReadIndicator = false + var isRecordingBlocked = false + private set + private var mHwEncoding = false + var notificationVisibility = 0 + + init { + if (s != null) { + isAllowPushNotifications = s.isAllowPushNotifications + isAllowPersistentNotification = s.isAllowPersistentNotification + isAllowSystemContacts = s.isAllowSystemContacts + isAllowPlaceSystemCalls = s.isAllowPlaceSystemCalls + isAllowOnStartup = s.isAllowOnStartup + isAllowTypingIndicator = s.isAllowTypingIndicator + isAllowReadIndicator = s.isAllowReadIndicator + isRecordingBlocked = s.isRecordingBlocked + mHwEncoding = s.mHwEncoding + notificationVisibility = s.notificationVisibility + } + } + + fun setAllowRingOnStartup(allowRingOnStartup: Boolean) { + isAllowOnStartup = allowRingOnStartup + } + + fun setBlockRecordIndicator(checked: Boolean) { + isRecordingBlocked = checked + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Uri.kt b/ring-android/libringclient/src/main/java/net/jami/model/Uri.kt index 6af5d13030fc353eff5d1488767c8cd561643d57..9f499633a157e0b236c4170ec5063badad70e8fe 100644 --- a/ring-android/libringclient/src/main/java/net/jami/model/Uri.kt +++ b/ring-android/libringclient/src/main/java/net/jami/model/Uri.kt @@ -120,7 +120,6 @@ class Uri : Serializable { private val VALID_IPV4_PATTERN = Pattern.compile(ipv4Pattern, Pattern.CASE_INSENSITIVE) private val VALID_IPV6_PATTERN = Pattern.compile(ipv6Pattern, Pattern.CASE_INSENSITIVE) - @JvmStatic fun fromString(uri: String): Uri { val m = URI_PATTERN.matcher(uri) return if (m.find()) { @@ -130,7 +129,6 @@ class Uri : Serializable { } } - @JvmStatic fun fromStringWithName(uriString: String): Tuple<Uri, String?> { val m = ANGLE_BRACKETS_PATTERN.matcher(uriString) return if (m.find()) { @@ -140,7 +138,6 @@ class Uri : Serializable { } } - @JvmStatic fun fromId(conversationId: String): Uri { return Uri(null, null, conversationId, null) } @@ -154,7 +151,6 @@ class Uri : Serializable { * @return `true` if the string is a value that is a valid IP address, * `false` otherwise. */ - @JvmStatic fun isIpAddress(ipAddress: String): Boolean { val m1 = VALID_IPV4_PATTERN.matcher(ipAddress) if (m1.matches()) { diff --git a/ring-android/libringclient/src/main/java/net/jami/mvp/AccountCreationModel.java b/ring-android/libringclient/src/main/java/net/jami/mvp/AccountCreationModel.java deleted file mode 100644 index f5ec2f39937a96a2a849cfa005e278dcdef5e455..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/mvp/AccountCreationModel.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.mvp; - -import net.jami.model.Account; - -import java.io.File; -import java.io.Serializable; - -import ezvcard.VCard; -import io.reactivex.rxjava3.core.Observable; -import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.subjects.BehaviorSubject; -import io.reactivex.rxjava3.subjects.Subject; - -public abstract class AccountCreationModel implements Serializable { - - private String mManagementServer = null; - private String mFullName = ""; - private String mUsername = ""; - private String mPassword = ""; - private String mPin = ""; - private File mArchive = null; - - private boolean link = false; - private boolean mPush = true; - transient private net.jami.model.Account newAccount = null; - transient private Object photo = null; - - transient private Observable<Account> account; - transient protected final Subject<AccountCreationModel> profile = BehaviorSubject.createDefault(this); - - public AccountCreationModel() { - } - - public String getFullName() { - return mFullName; - } - - public void setFullName(String fullName) { - this.mFullName = fullName; - profile.onNext(this); - } - - public Object getPhoto() { - return photo; - } - - public void setPhoto(Object photo) { - this.photo = photo; - profile.onNext(this); - } - - public String getUsername() { - return mUsername; - } - - public void setUsername(String username) { - this.mUsername = username; - } - - public String getPassword() { - return mPassword; - } - - public void setPassword(String password) { - mPassword = password; - } - - public String getPin() { - return mPin; - } - - public void setPin(String pin) { - mPin = pin.toUpperCase(); - } - - public boolean isPush() { - return mPush; - } - - public void setPush(boolean push) { - mPush = push; - } - - public boolean isLink() { - return link; - } - - public void setLink(boolean link) { - this.link = link; - } - - public void setArchive(File archive) { - mArchive = archive; - } - - public File getArchive() { - return mArchive; - } - - public void setManagementServer(String server) { - mManagementServer = server; - } - - public String getManagementServer() { - return mManagementServer; - } - - public void setNewAccount(net.jami.model.Account account) { - newAccount = account; - profile.onNext(this); - } - - public net.jami.model.Account getNewAccount() { - return newAccount; - } - - public void setAccountObservable(Observable<net.jami.model.Account> account) { - this.account = account; - } - - public Observable<Account> getAccountObservable() { - return account; - } - - public abstract Single<VCard> toVCard(); - - public Observable<AccountCreationModel> getProfileUpdates() { - return profile; - } -} diff --git a/ring-android/libringclient/src/main/java/net/jami/mvp/RootPresenter.java b/ring-android/libringclient/src/main/java/net/jami/mvp/RootPresenter.kt similarity index 54% rename from ring-android/libringclient/src/main/java/net/jami/mvp/RootPresenter.java rename to ring-android/libringclient/src/main/java/net/jami/mvp/RootPresenter.kt index 8b75cbe6ec6ab3d840f9317da41af448143e438e..47404004c7fa769017414f4834e4ceb90fb3314a 100644 --- a/ring-android/libringclient/src/main/java/net/jami/mvp/RootPresenter.java +++ b/ring-android/libringclient/src/main/java/net/jami/mvp/RootPresenter.kt @@ -17,43 +17,31 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -package net.jami.mvp; +package net.jami.mvp -import java.lang.ref.WeakReference; +import io.reactivex.rxjava3.disposables.CompositeDisposable +import java.lang.ref.WeakReference -import io.reactivex.rxjava3.disposables.CompositeDisposable; - -public abstract class RootPresenter<T> { - - protected CompositeDisposable mCompositeDisposable = new CompositeDisposable(); - - public RootPresenter() { } - - private WeakReference<T> mView; - - public void bindView(T view) { - mView = new WeakReference<>(view); +abstract class RootPresenter<T> { + @JvmField + protected var mCompositeDisposable = CompositeDisposable() + private var mView: WeakReference<T>? = null + open fun bindView(view: T) { + mView = WeakReference(view) } - public void unbindView() { - if (mView != null) { - mView.clear(); - mView = null; + open fun unbindView() { + mView?.let { view -> + view.clear() + mView = null } - mCompositeDisposable.clear(); + mCompositeDisposable.clear() } - public void onDestroy() { - mCompositeDisposable.dispose(); + open fun onDestroy() { + mCompositeDisposable.dispose() } - public T getView() { - if (mView != null) { - return mView.get(); - } - return null; - } - -} - - + val view: T? + get() = mView?.get() +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationPresenter.java b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationPresenter.java deleted file mode 100644 index 92d31e72dd848fb8212dc38de9e12a6b92004ed2..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationPresenter.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -package net.jami.navigation; - -import java.io.File; - -import javax.inject.Inject; -import javax.inject.Named; - -import net.jami.model.Account; -import net.jami.mvp.RootPresenter; -import net.jami.services.AccountService; -import net.jami.services.DeviceRuntimeService; -import net.jami.services.HardwareService; -import net.jami.utils.Log; -import net.jami.utils.StringUtils; -import net.jami.utils.Tuple; -import net.jami.utils.VCardUtils; -import ezvcard.property.Photo; -import ezvcard.property.RawProperty; -import ezvcard.property.Uid; -import io.reactivex.rxjava3.core.Scheduler; -import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.schedulers.Schedulers; - -public class HomeNavigationPresenter extends RootPresenter<HomeNavigationView> { - - private static final String TAG = HomeNavigationPresenter.class.getSimpleName(); - - private final AccountService mAccountService; - private final DeviceRuntimeService mDeviceRuntimeService; - private final HardwareService mHardwareService; - - @Inject - @Named("UiScheduler") - protected Scheduler mUiScheduler; - - @Inject - public HomeNavigationPresenter(AccountService accountService, - HardwareService hardwareService, - DeviceRuntimeService deviceRuntimeService) { - mAccountService = accountService; - mHardwareService = hardwareService; - mDeviceRuntimeService = deviceRuntimeService; - } - - @Override - public void bindView(HomeNavigationView view) { - super.bindView(view); - mCompositeDisposable.add(mAccountService.getCurrentProfileAccountSubject() - .switchMap(account -> account.getLoadedProfileObservable() - .map(alias -> new Tuple<>(account, alias))) - .observeOn(mUiScheduler) - .subscribe(alias -> { - HomeNavigationView v = getView(); - if (v != null) - v.showViewModel(new HomeNavigationViewModel(alias.first, alias.second.first)); - }, e -> Log.e(TAG, "Error loading account list !", e))); - mCompositeDisposable.add(mAccountService.getObservableAccounts() - .observeOn(mUiScheduler) - .subscribe(account -> { - HomeNavigationView v = getView(); - if (v != null) - v.updateModel(account); - }, e -> Log.e(TAG, "Error loading account list !", e))); - } - - public void setAccountOrder(Account selectedAccount) { - mAccountService.setCurrentAccount(selectedAccount); - } - - public void saveVCardPhoto(Single<Photo> photo) { - Account account = mAccountService.getCurrentAccount(); - String accountId = account.getAccountID(); - String ringId = account.getUsername(); - File filesDir = mDeviceRuntimeService.provideFilesDir(); - - mCompositeDisposable.add(Single.zip( - VCardUtils.loadLocalProfileFromDiskWithDefault(filesDir, accountId).subscribeOn(Schedulers.io()), - photo.subscribeOn(Schedulers.io()), - (vcard, pic) -> { - vcard.setUid(new Uid(ringId)); - vcard.removeProperties(Photo.class); - vcard.addPhoto(pic); - vcard.removeProperties(RawProperty.class); - return vcard; - }) - .flatMap(vcard -> VCardUtils.saveLocalProfileToDisk(vcard, accountId, filesDir)) - .subscribeOn(Schedulers.io()) - .subscribe(vcard -> { - account.resetProfile(); - mAccountService.refreshAccounts(); - getView().setPhoto(account); - }, e -> Log.e(TAG, "Error saving vCard !", e))); - } - - public void saveVCardFormattedName(String username) { - Account account = mAccountService.getCurrentAccount(); - String accountId = account.getAccountID(); - File filesDir = mDeviceRuntimeService.provideFilesDir(); - - mCompositeDisposable.add(VCardUtils - .loadLocalProfileFromDiskWithDefault(filesDir, accountId) - .doOnSuccess(vcard -> { - vcard.setFormattedName(username); - vcard.removeProperties(RawProperty.class); - }) - .flatMap(vcard -> VCardUtils.saveLocalProfileToDisk(vcard, accountId, filesDir)) - .subscribeOn(Schedulers.io()) - .subscribe(vcard -> { - account.resetProfile(); - mAccountService.refreshAccounts(); - bindView(getView()); - }, e -> Log.e(TAG, "Error saving vCard !", e))); - } - - public void saveVCard(Account account, String username, Single<Photo> photo) { - String accountId = account.getAccountID(); - String ringId = account.getUsername(); - File filesDir = mDeviceRuntimeService.provideFilesDir(); - mCompositeDisposable.add(Single.zip( - VCardUtils.loadLocalProfileFromDiskWithDefault(filesDir, accountId).subscribeOn(Schedulers.io()), - photo, (vcard, pic) -> { - vcard.setUid(new Uid(ringId)); - if (!StringUtils.isEmpty(username)) { - vcard.setFormattedName(username); - } - vcard.removeProperties(Photo.class); - vcard.addPhoto(pic); - vcard.removeProperties(RawProperty.class); - return vcard; - }) - .flatMap(vcard -> VCardUtils.saveLocalProfileToDisk(vcard, accountId, filesDir)) - .subscribeOn(Schedulers.io()) - .subscribe(vcard -> { - account.resetProfile(); - mAccountService.refreshAccounts(); - }, e -> Log.e(TAG, "Error saving vCard !", e))); - } - - public String getUri(Account account, CharSequence defaultNameSip) { - if (account.isIP2IP()) { - return defaultNameSip.toString(); - } - return account.getDisplayUri(); - } - - public void cameraClicked() { - boolean hasPermission = mDeviceRuntimeService.hasVideoPermission() && - mDeviceRuntimeService.hasWriteExternalStoragePermission(); - if (hasPermission) { - getView().gotToImageCapture(); - } else { - getView().askCameraPermission(); - } - } - - public void galleryClicked() { - boolean hasPermission = mDeviceRuntimeService.hasGalleryPermission(); - if (hasPermission) { - getView().goToGallery(); - } else { - getView().askGalleryPermission(); - } - } - - public void cameraPermissionChanged(boolean isGranted) { - if (isGranted && mHardwareService.isVideoAvailable()) { - mHardwareService.initVideo() - .onErrorComplete() - .subscribe(); - } - } -} diff --git a/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..21b129d2d72435f53a1486bf70586031d70af024 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationPresenter.kt @@ -0,0 +1,160 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.navigation + +import ezvcard.VCard +import ezvcard.property.Photo +import ezvcard.property.RawProperty +import ezvcard.property.Uid +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.core.Single +import io.reactivex.rxjava3.schedulers.Schedulers +import net.jami.model.Account +import net.jami.mvp.RootPresenter +import net.jami.services.AccountService +import net.jami.services.DeviceRuntimeService +import net.jami.services.HardwareService +import net.jami.services.VCardService +import net.jami.utils.Log +import net.jami.utils.StringUtils.isEmpty +import net.jami.utils.VCardUtils +import javax.inject.Inject +import javax.inject.Named + +class HomeNavigationPresenter @Inject constructor( + private val mAccountService: AccountService, + private val mHardwareService: HardwareService, + private val mDeviceRuntimeService: DeviceRuntimeService, + private val mVCardService: VCardService, + @param:Named("UiScheduler") + private val mUiScheduler: Scheduler +) : RootPresenter<HomeNavigationView>() { + + override fun bindView(view: HomeNavigationView) { + super.bindView(view) + mCompositeDisposable.add(mAccountService.currentProfileAccountSubject + .observeOn(mUiScheduler) + .subscribe({ accountProfile -> + this.view?.showViewModel(HomeNavigationViewModel(accountProfile.first, accountProfile.second)) + }) { e: Throwable -> Log.e(TAG, "Error loading account list !", e) }) + } + + fun saveVCardPhoto(photo: Single<Photo>) { + val account = mAccountService.currentAccount!! + val accountId = account.accountID + val ringId = account.username + val filesDir = mDeviceRuntimeService.provideFilesDir() + mCompositeDisposable.add(Single.zip( + VCardUtils.loadLocalProfileFromDiskWithDefault(filesDir, accountId).subscribeOn(Schedulers.io()), + photo.subscribeOn(Schedulers.io()), + { vcard: VCard, pic: Photo -> + vcard.uid = Uid(ringId) + vcard.removeProperties(Photo::class.java) + vcard.addPhoto(pic) + vcard.removeProperties(RawProperty::class.java) + vcard + }) + .subscribeOn(Schedulers.io()) + .subscribe({ vcard -> + account.loadedProfile = mVCardService.loadVCardProfile(vcard).cache() + VCardUtils.saveLocalProfileToDisk(vcard, accountId, filesDir) + .subscribeOn(Schedulers.io()) + .subscribe() + }) { e: Throwable -> Log.e(TAG, "Error saving vCard !", e) }) + } + + fun saveVCardFormattedName(username: String?) { + val account = mAccountService.currentAccount!! + val accountId = account.accountID + val filesDir = mDeviceRuntimeService.provideFilesDir() + mCompositeDisposable.add(VCardUtils.loadLocalProfileFromDiskWithDefault(filesDir, accountId) + .doOnSuccess { vcard: VCard -> + vcard.setFormattedName(username) + vcard.removeProperties(RawProperty::class.java) + } + .subscribeOn(Schedulers.io()) + .subscribe({ vcard -> + account.loadedProfile = mVCardService.loadVCardProfile(vcard).cache() + VCardUtils.saveLocalProfileToDisk(vcard, accountId, filesDir) + .subscribeOn(Schedulers.io()) + .subscribe() + }) { e: Throwable -> Log.e(TAG, "Error saving vCard !", e) }) + } + + fun saveVCard(account: Account, username: String?, photo: Single<Photo>) { + val accountId = account.accountID + val ringId = account.username + val filesDir = mDeviceRuntimeService.provideFilesDir() + mCompositeDisposable.add(Single.zip( + VCardUtils.loadLocalProfileFromDiskWithDefault(filesDir, accountId).subscribeOn(Schedulers.io()), + photo, { vcard: VCard, pic: Photo -> + vcard.uid = Uid(ringId) + if (!isEmpty(username)) { + vcard.setFormattedName(username) + } + vcard.removeProperties(Photo::class.java) + vcard.addPhoto(pic) + vcard.removeProperties(RawProperty::class.java) + account.loadedProfile = mVCardService.loadVCardProfile(vcard).cache() + vcard + }) + .flatMap { vcard -> VCardUtils.saveLocalProfileToDisk(vcard, accountId, filesDir) } + .subscribeOn(Schedulers.io()) + .subscribe({}) { e: Throwable -> Log.e(TAG, "Error saving vCard !", e) }) + } + + fun getUri(account: Account, defaultNameSip: CharSequence): String? { + return if (account.isIP2IP) { + defaultNameSip.toString() + } else account.displayUri + } + + fun cameraClicked() { + val hasPermission = mDeviceRuntimeService.hasVideoPermission() && + mDeviceRuntimeService.hasWriteExternalStoragePermission() + if (hasPermission) { + view?.gotToImageCapture() + } else { + view?.askCameraPermission() + } + } + + fun galleryClicked() { + val hasPermission = mDeviceRuntimeService.hasGalleryPermission() + if (hasPermission) { + view?.goToGallery() + } else { + view?.askGalleryPermission() + } + } + + fun cameraPermissionChanged(isGranted: Boolean) { + if (isGranted && mHardwareService.isVideoAvailable) { + mHardwareService.initVideo() + .onErrorComplete() + .subscribe() + } + } + + companion object { + private val TAG = HomeNavigationPresenter::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationView.java b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationView.java deleted file mode 100644 index fc87c0309bb5ef278b3f7f7749d72bd2ba4a7b17..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationView.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.navigation; - -import net.jami.model.Account; - -public interface HomeNavigationView { - - void showViewModel(HomeNavigationViewModel viewModel); - - void updateModel(Account account); - - void gotToImageCapture(); - - void askCameraPermission(); - - void goToGallery(); - - void askGalleryPermission(); - - void setPhoto(Account account); - -} diff --git a/ring-android/libringclient/src/main/java/net/jami/account/JamiLinkAccountView.java b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationView.kt similarity index 74% rename from ring-android/libringclient/src/main/java/net/jami/account/JamiLinkAccountView.java rename to ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationView.kt index 67b13655f44cee807387b1036818fdc08455116a..3c8f351449801b6908db3b827ad1465bcff6c33a 100644 --- a/ring-android/libringclient/src/main/java/net/jami/account/JamiLinkAccountView.java +++ b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationView.kt @@ -17,17 +17,15 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -package net.jami.account; +package net.jami.navigation -import net.jami.mvp.AccountCreationModel; +import net.jami.model.Account -public interface JamiLinkAccountView { - - void enableLinkButton(boolean enable); - - void showPin(boolean show); - - void createAccount(AccountCreationModel accountCreationModel); - - void cancel(); -} +interface HomeNavigationView { + fun showViewModel(viewModel: HomeNavigationViewModel) + fun gotToImageCapture() + fun askCameraPermission() + fun goToGallery() + fun askGalleryPermission() + //fun setPhoto(account: Account) +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationViewModel.kt b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationViewModel.kt index 6589fd507b97d0e09a3fce65550cd2d649fdad73..9300f15ffa76429437d9446a713a474faf01c66c 100644 --- a/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationViewModel.kt +++ b/ring-android/libringclient/src/main/java/net/jami/navigation/HomeNavigationViewModel.kt @@ -21,5 +21,6 @@ package net.jami.navigation import net.jami.model.Account +import net.jami.model.Profile -class HomeNavigationViewModel(val account: Account, val alias: String?) \ No newline at end of file +class HomeNavigationViewModel(val account: Account, val profile: Profile) \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/services/AccountService.kt b/ring-android/libringclient/src/main/java/net/jami/services/AccountService.kt index 31c70d754558b46dbcfb89f1ad3098523a40f8e0..f14c066f2aef883f2fa955254ea6fe26d7303bee 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/AccountService.kt +++ b/ring-android/libringclient/src/main/java/net/jami/services/AccountService.kt @@ -68,6 +68,10 @@ class AccountService( */ var currentAccount: Account? = null set(account) { + if (field == null) { + field = account + return + } if (field === account) return field = account!! @@ -78,7 +82,7 @@ class AccountService( val selectedID = account.accountID orderedAccountIdList.add(selectedID) for (a in accounts) { - if (a.accountID.contentEquals(selectedID)) { + if (a.accountID == selectedID) { continue } orderedAccountIdList.add(a.accountID) @@ -336,18 +340,15 @@ class AccountService( } if (enabled) { for (contact in account.contacts.values) { - if (!contact.isUsernameLoaded) JamiService.lookupAddress( - accountId, - "", - contact.uri.rawRingId - ) + if (!contact.isUsernameLoaded) + JamiService.lookupAddress(accountId, "", contact.uri.rawRingId) } } } } mHasSipAccount = hasSip mHasRingAccount = hasJami - if (!newAccounts.isEmpty()) { + if (newAccounts.isNotEmpty()) { val newAccount = newAccounts[0] if (currentAccount !== newAccount) { currentAccount = newAccount @@ -386,7 +387,7 @@ class AccountService( * @param map the account details * @return the created Account */ - fun addAccount(map: Map<String?, String?>?): Observable<Account?> { + fun addAccount(map: Map<String, String>): Observable<Account> { return Observable.fromCallable { val accountId = JamiService.addAccount(StringMap.toSwig(map)) if (StringUtils.isEmpty(accountId)) { @@ -450,6 +451,12 @@ class AccountService( return observableAccounts.filter { acc -> acc.accountID == accountId } } + fun getObservableAccountProfile(accountId: String): Observable<Pair<Account, Profile>> { + return getObservableAccount(accountId).flatMap { a: Account -> + mVCardService.loadProfile(a).map { profile -> Pair(a, profile) } + } + } + fun getObservableAccount(accountId: String): Observable<Account> { return Observable.fromCallable<Account> { getAccount(accountId) } .concatWith(getObservableAccountUpdates(accountId)) @@ -460,9 +467,9 @@ class AccountService( .concatWith(observableAccounts.filter { acc -> acc === account }) } - val currentProfileAccountSubject: Observable<Account> - get() = currentAccountSubject.flatMapSingle { a: Account -> - mVCardService.loadProfile(a).firstOrError().map { p: Tuple<String?, Any?>? -> a } + val currentProfileAccountSubject: Observable<Pair<Account, Profile>> + get() = currentAccountSubject.flatMap { a: Account -> + mVCardService.loadProfile(a).map { profile -> Pair(a, profile) } } fun subscribeBuddy(accountID: String?, uri: String?, flag: Boolean) { @@ -571,7 +578,7 @@ class AccountService( * * @param accountOrder The ordered list of account ids */ - fun setAccountOrder(accountOrder: List<String>) { + private fun setAccountOrder(accountOrder: List<String>) { mExecutor.execute { val order = StringBuilder() for (accountId in accountOrder) { @@ -1116,17 +1123,24 @@ class AccountService( val account = getAccount(accountId) ?: return mVCardService.saveVCardProfile(accountId, account.uri, name, photo) .subscribeOn(Schedulers.io()) - .subscribe({ account.resetProfile() }) { e -> Log.e(TAG, "Error saving profile", e) } + .subscribe({ vcard -> account.loadedProfile = mVCardService.loadVCardProfile(vcard).cache() }) + { e -> Log.e(TAG, "Error saving profile", e) } } fun profileReceived(accountId: String, peerId: String, vcardPath: String) { val account = getAccount(accountId) ?: return Log.w(TAG, "profileReceived: $accountId, $peerId, $vcardPath") val contact = account.getContactFromCache(peerId) - mVCardService.peerProfileReceived(accountId, peerId, File(vcardPath)) - .subscribe({ profile: Tuple<String?, Any?> -> - contact.setProfile(profile.first, profile.second) - }) { e -> Log.e(TAG, "Error saving contact profile", e) } + if (contact.isUser) { + mVCardService.accountProfileReceived(accountId, File(vcardPath)) + .subscribe({ profile: Profile -> + account.loadedProfile = Single.just(profile) + }) { e -> Log.e(TAG, "Error saving contact profile", e) } + } else { + mVCardService.peerProfileReceived(accountId, peerId, File(vcardPath)) + .subscribe({ profile -> contact.setProfile(profile) }) + { e -> Log.e(TAG, "Error saving contact profile", e) } + } } fun incomingAccountMessage(accountId: String, messageId: String?, callId: String?, from: String, messages: Map<String, String>) { @@ -1242,12 +1256,7 @@ class AccountService( // VCardUtils.savePeerProfileToDisk(vcard, accountId, from + ".vcf", mDeviceRuntimeService.provideFilesDir()); mVCardService.loadVCardProfile(vcard) .subscribeOn(Schedulers.computation()) - .subscribe { profile: Tuple<String?, Any?> -> - contact.setProfile( - profile.first, - profile.second - ) - } + .subscribe { profile -> contact.setProfile(profile) } } } account.addRequest(request) @@ -1258,8 +1267,7 @@ class AccountService( } fun contactAdded(accountId: String, uri: String, confirmed: Boolean) { - val account = getAccount(accountId) - if (account != null) { + getAccount(accountId)?.let { account -> val details: Map<String, String> = JamiService.getContactDetails(accountId, uri) val contact = account.addContact(details) val conversationUri = contact.conversationUri.blockingFirst() diff --git a/ring-android/libringclient/src/main/java/net/jami/services/CallService.kt b/ring-android/libringclient/src/main/java/net/jami/services/CallService.kt index 48509835328881b77d4b971f4b5809f73e205487..f665ed4719ac71befec024e32e4b7af7e9441366 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/CallService.kt +++ b/ring-android/libringclient/src/main/java/net/jami/services/CallService.kt @@ -33,8 +33,6 @@ import net.jami.model.Call.CallStatus import net.jami.model.Conference import net.jami.model.Conference.ParticipantInfo import net.jami.model.Uri -import net.jami.model.Uri.Companion.fromString -import net.jami.model.Uri.Companion.fromStringWithName import net.jami.utils.Log import net.jami.utils.StringUtils.isEmpty import java.util.* @@ -97,7 +95,7 @@ class CallService( val newInfo: MutableList<ParticipantInfo> = ArrayList(info.size) if (conference.isConference) { for (i in info) { - val call = conference.findCallByContact(fromString(i["uri"]!!)) + val call = conference.findCallByContact(Uri.fromString(i["uri"]!!)) if (call != null) { val confInfo = ParticipantInfo(call, call.contact!!, i) if (confInfo.isEmpty) { @@ -116,7 +114,7 @@ class CallService( } else { val account = mAccountService.getAccount(conference.call!!.account!!)!! for (i in info) { - val confInfo = ParticipantInfo(null, account.getContactFromCache(fromString(i["uri"]!!)), i) + val confInfo = ParticipantInfo(null, account.getContactFromCache(Uri.fromString(i["uri"]!!)), i) if (confInfo.isEmpty) { Log.w(TAG, "onConferenceInfoUpdated: ignoring empty entry $i") continue @@ -136,7 +134,7 @@ class CallService( fun setConfMaximizedParticipant(confId: String, uri: Uri) { mExecutor.execute { - JamiService.setActiveParticipant(confId, uri?.rawRingId ?: "") + JamiService.setActiveParticipant(confId, uri.rawRingId ?: "") JamiService.setConferenceLayout(confId, 1) } } @@ -248,6 +246,7 @@ class CallService( } fun hangUp(callId: String) { + Log.i(TAG, "hangUp() called... $callId", Exception()) mExecutor.execute { Log.i(TAG, "hangUp() running... $callId") JamiService.hangUp(callId) @@ -385,7 +384,7 @@ class CallService( mExecutor.execute { JamiService.setRecordPath(path) } } - fun toggleRecordingCall(id: String?): Boolean { + fun toggleRecordingCall(id: String): Boolean { mExecutor.execute { JamiService.toggleRecording(id) } return false } @@ -399,7 +398,7 @@ class CallService( mExecutor.execute { JamiService.stopRecordedFilePlayback() } } - fun sendTextMessage(callId: String, msg: String?) { + fun sendTextMessage(callId: String, msg: String) { mExecutor.execute { Log.i(TAG, "sendTextMessage() thread running...") val messages = StringMap() @@ -487,9 +486,9 @@ class CallService( } call.setCallState(callState) val account = mAccountService.getAccount(call.account!!)!! - val contact = mContactService.findContact(account, fromString(call.contactNumber!!)) + val contact = mContactService.findContact(account, Uri.fromString(call.contactNumber!!)) val registeredName = callDetails[Call.KEY_REGISTERED_NAME] - if (registeredName != null && !registeredName.isEmpty()) { + if (registeredName != null && registeredName.isNotEmpty()) { contact.setUsername(registeredName) } val conversation = account.getByUri(contact.conversationUri.blockingFirst()) @@ -536,7 +535,7 @@ class CallService( fun incomingCall(accountId: String, callId: String, from: String) { Log.d(TAG, "incoming call: $accountId, $callId, $from") - val call = addCall(accountId, callId, fromStringWithName(from).first, Call.Direction.INCOMING) + val call = addCall(accountId, callId, Uri.fromStringWithName(from).first, Call.Direction.INCOMING) callSubject.onNext(call) updateConnectionCount() } @@ -720,8 +719,7 @@ class CallService( fun conferenceRemoved(confId: String) { Log.d(TAG, "conference removed: $confId") - val conf = currentConferences.remove(confId) - if (conf != null) { + currentConferences.remove(confId)?.let { conf -> for (call in conf.participants) { call.confId = null } diff --git a/ring-android/libringclient/src/main/java/net/jami/services/ContactService.kt b/ring-android/libringclient/src/main/java/net/jami/services/ContactService.kt index 755ea2e3cb010efbe5a11ea28c90fdddb42f35c4..9d1b17422667cd9f5ca71fbc469e6306945ca8e5 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/ContactService.kt +++ b/ring-android/libringclient/src/main/java/net/jami/services/ContactService.kt @@ -49,7 +49,7 @@ abstract class ContactService( protected abstract fun findContactByNumberFromSystem(number: String): Contact? abstract fun loadContactData(contact: Contact, accountId: String): Completable abstract fun saveVCardContactData(contact: Contact, accountId: String, vcard: VCard) - abstract fun saveVCardContact(accountId: String, uri: String, displayName: String, pictureB64: String): Single<VCard> + abstract fun saveVCardContact(accountId: String, uri: String?, displayName: String?, pictureB64: String?): Single<VCard> /** * Load contacts from system and generate a local contact cache @@ -99,9 +99,7 @@ abstract class ContactService( .replay(1) .refCount() } - return if (withPresence) Observable.combineLatest( - contact.updates, - contact.presenceUpdates, + return if (withPresence) Observable.combineLatest(contact.updates, contact.presenceUpdates, { c: Contact, p: Boolean -> c }) else contact.updates!! } } diff --git a/ring-android/libringclient/src/main/java/net/jami/services/ConversationFacade.kt b/ring-android/libringclient/src/main/java/net/jami/services/ConversationFacade.kt index 1503eca774d4b031c4ac984775bf861c1240d695..c759e5494decd1b136e1ff24bca17df595075d41 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/ConversationFacade.kt +++ b/ring-android/libringclient/src/main/java/net/jami/services/ConversationFacade.kt @@ -36,7 +36,6 @@ import net.jami.services.AccountService.RegisteredName import net.jami.smartlist.SmartListViewModel import net.jami.utils.FileUtils.moveFile import net.jami.utils.Log -import net.jami.utils.Tuple import java.io.File import java.util.* import java.util.concurrent.TimeUnit @@ -129,9 +128,9 @@ class ConversationFacade( }.ignoreElement() } - fun sendTextMessage(c: Conversation, conf: Conference, txt: String?) { + fun sendTextMessage(c: Conversation, conf: Conference, txt: String) { mCallService.sendTextMessage(conf.id, txt) - val message = TextMessage(null, c.accountId, conf.id, c, txt!!) + val message = TextMessage(null, c.accountId, conf.id, c, txt) message.read() mHistoryService.insertInteraction(c.accountId, c, message).subscribe() c.addTextMessage(message) @@ -152,29 +151,18 @@ class ConversationFacade( return Completable.complete() } return Single.fromCallable { - val transfer = DataTransfer( - conversation, - to.rawRingId, - conversation.accountId, - file.name, - true, - file.length(), - 0, - null - ) + val transfer = DataTransfer(conversation, to.rawRingId, conversation.accountId, file.name, true, file.length(), 0, null) mHistoryService.insertInteraction(conversation.accountId, conversation, transfer).blockingAwait() transfer.destination = mDeviceRuntimeService.getConversationDir(conversation.uri.rawRingId) transfer } .flatMap { t: DataTransfer -> mAccountService.sendFile(file, t) } - .flatMapCompletable { transfer: DataTransfer -> - Completable.fromAction { - val destination = File(transfer.destination, transfer.storagePath) - if (!mDeviceRuntimeService.hardLinkOrCopy(file, destination)) { - Log.e(TAG, "sendFile: can't move file to $destination") - } + .flatMapCompletable { transfer: DataTransfer -> Completable.fromAction { + val destination = File(transfer.destination, transfer.storagePath) + if (!mDeviceRuntimeService.hardLinkOrCopy(file, destination)) { + Log.e(TAG, "sendFile: can't move file to $destination") } - } + } } .subscribeOn(Schedulers.io()) } @@ -210,9 +198,9 @@ class ConversationFacade( val accountId = message.account!! mDisposableBag.add( Completable.mergeArrayDelayError(mCallService.cancelMessage(accountId, message.id.toLong()).subscribeOn(Schedulers.io())) - .andThen(startConversation(accountId, fromString(message.conversation!!.participant))) + .andThen(startConversation(accountId, fromString(message.conversation!!.participant!!))) .subscribe({ c: Conversation -> c.removeInteraction(message) } - ) { e: Throwable? -> Log.e(TAG, "Can't cancel message sending", e) }) + ) { e: Throwable -> Log.e(TAG, "Can't cancel message sending", e) }) } /** @@ -273,19 +261,20 @@ class ConversationFacade( fun getSmartList(currentAccount: Observable<Account>, hasPresence: Boolean): Observable<List<Observable<SmartListViewModel>>> { return currentAccount.switchMap { account: Account -> account.getConversationsSubject() - .switchMapSingle { conversations -> Observable.fromIterable(conversations) + .switchMapSingle { conversations -> if (conversations.isEmpty()) Single.just(emptyList()) + else Observable.fromIterable(conversations) .map { conversation: Conversation -> observeConversation(account, conversation, hasPresence) } - .toList() } } + .toList(conversations.size) } } } - fun getContactList(currentAccount: Observable<Account>): Observable<List<SmartListViewModel>> { + fun getContactList(currentAccount: Observable<Account>): Observable<MutableList<SmartListViewModel>> { return currentAccount.switchMap { account: Account -> account.getConversationsSubject() - .switchMapSingle { conversations: List<Conversation>? -> - Observable.fromIterable(conversations) + .switchMapSingle { conversations: List<Conversation> -> if (conversations.isEmpty()) Single.just(ArrayList()) + else Observable.fromIterable(conversations) .filter { conversation: Conversation -> !conversation.isSwarm } .map { conversation: Conversation -> SmartListViewModel(conversation, false) } - .toList() + .toList(conversations.size) } } } @@ -304,7 +293,7 @@ class ConversationFacade( val pendingList: Observable<List<Observable<SmartListViewModel>>> get() = getPendingList(mAccountService.currentAccountSubject) - val contactList: Observable<List<SmartListViewModel>> + val contactList: Observable<MutableList<SmartListViewModel>> get() = getContactList(mAccountService.currentAccountSubject) private fun getSearchResults(account: Account, query: String): Single<List<Observable<SmartListViewModel>>> { @@ -409,7 +398,7 @@ class ConversationFacade( */ private fun getConversationHistory(conversation: Conversation): Single<Conversation> { Log.d(TAG, "getConversationHistory() " + conversation.accountId + " " + conversation.uri) - return mHistoryService.getConversationHistory(conversation.accountId, conversation.id) + return mHistoryService.getConversationHistory(conversation.accountId, conversation.id!!) .map { loadedConversation: List<Interaction> -> conversation.clearHistory(true) conversation.setHistory(loadedConversation) @@ -464,7 +453,7 @@ class ConversationFacade( .firstOrError() .subscribeOn(Schedulers.io()) .subscribe({ c: List<Conversation> -> updateTextNotifications(txt.account!!, c) }) - { e: Throwable -> Log.e(TAG, e.message) } + { e: Throwable -> Log.e(TAG, e.message!!) } } fun acceptRequest(accountId: String, contactUri: Uri) { @@ -479,11 +468,12 @@ class ConversationFacade( } private fun handleDataTransferEvent(transfer: DataTransfer) { - val conversation = mAccountService.getAccount(transfer.account!!)!!.onDataTransferEvent(transfer) + val account = transfer.account!! + val conversation = mAccountService.getAccount(account)!!.onDataTransferEvent(transfer) val status = transfer.status - Log.d(TAG, "handleDataTransferEvent $status " + transfer.canAutoAccept(mPreferencesService.getMaxFileAutoAccept(transfer.account))) + Log.d(TAG, "handleDataTransferEvent $status " + transfer.canAutoAccept(mPreferencesService.getMaxFileAutoAccept(account))) if (status === InteractionStatus.TRANSFER_AWAITING_HOST || status === InteractionStatus.FILE_AVAILABLE) { - if (transfer.canAutoAccept(mPreferencesService.getMaxFileAutoAccept(transfer.account))) { + if (transfer.canAutoAccept(mPreferencesService.getMaxFileAutoAccept(account))) { mAccountService.acceptFileTransfer(conversation, transfer.fileId!!, transfer) return } @@ -505,18 +495,13 @@ class ConversationFacade( val conversationId = call.conversationId Log.w(TAG, "CallStateChange " + call.daemonIdString + " conversationId:" + conversationId) val conversation = if (conversationId == null) - if (contact == null) null else account.getByUri(contact.uri) + if (contact == null) return else account.getByUri(contact.uri)!! else - account.getSwarm(conversationId) - var conference: Conference? = null - if (conversation != null) { - conference = conversation.getConference(call.daemonIdString) - if (conference == null) { - if (newState === CallStatus.OVER) return - conference = Conference(call) - conversation.addConference(conference) - account.updated(conversation) - } + account.getSwarm(conversationId) ?: return + val conference: Conference = conversation.getConference(call.daemonIdString) ?: Conference(call).apply { + if (newState === CallStatus.OVER) return@onCallStateChange + conversation.addConference(this) + account.updated(conversation) } Log.w(TAG, "CALL_STATE_CHANGED : updating call state to $newState") if ((call.isRinging || newState === CallStatus.CURRENT) && call.timestamp == 0L) { @@ -526,8 +511,7 @@ class ConversationFacade( mNotificationService.handleCallNotification(conference, false) mHardwareService.setPreviewSettings() } else if (newState === CallStatus.CURRENT && call.isIncoming - || newState === CallStatus.RINGING && !call.isIncoming - ) { + || newState === CallStatus.RINGING && !call.isIncoming) { mNotificationService.handleCallNotification(conference, false) mAccountService.sendProfile(call.daemonIdString!!, call.account!!) } else if (newState === CallStatus.HUNGUP || newState === CallStatus.BUSY || newState === CallStatus.FAILURE || newState === CallStatus.OVER) { @@ -655,12 +639,12 @@ class ConversationFacade( .subscribe()) mDisposableBag.add(mAccountService.observableAccountList .switchMap { accounts: List<Account> -> - val r: MutableList<Observable<Tuple<Account, ContactLocationEntry>>> = ArrayList(accounts.size) - for (a in accounts) r.add(a.locationUpdates.map { s: ContactLocationEntry -> Tuple(a, s) }) + val r: MutableList<Observable<Pair<Account, ContactLocationEntry>>> = ArrayList(accounts.size) + for (a in accounts) r.add(a.locationUpdates.map { s: ContactLocationEntry -> Pair(a, s) }) Observable.merge(r) } .distinctUntilChanged() - .subscribe { t: Tuple<Account, ContactLocationEntry> -> + .subscribe { t: Pair<Account, ContactLocationEntry> -> Log.e(TAG, "Location reception started for " + t.second.contact) mNotificationService.showLocationNotification(t.first, t.second.contact) mDisposableBag.add(t.second.location.doOnComplete { diff --git a/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java b/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java deleted file mode 100644 index d947844bf223440cf3c370cbfb8cd3d6ec7f97a3..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java +++ /dev/null @@ -1,423 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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 <https://www.gnu.org/licenses/>. - */ -package net.jami.services; - -import net.jami.daemon.Blob; -import net.jami.daemon.Callback; -import net.jami.daemon.ConfigurationCallback; -import net.jami.daemon.ConversationCallback; -import net.jami.daemon.DataTransferCallback; -import net.jami.daemon.IntVect; -import net.jami.daemon.IntegerMap; -import net.jami.daemon.JamiService; -import net.jami.daemon.PresenceCallback; -import net.jami.daemon.StringMap; -import net.jami.daemon.StringVect; -import net.jami.daemon.UintVect; -import net.jami.daemon.VectMap; -import net.jami.daemon.VideoCallback; -import net.jami.model.Uri; -import net.jami.utils.Log; - -import java.util.Map; -import java.util.concurrent.ScheduledExecutorService; - -import javax.inject.Inject; -import javax.inject.Named; - -public class DaemonService { - - private static final String TAG = DaemonService.class.getSimpleName(); - - @Named("DaemonExecutor") - private final ScheduledExecutorService mExecutor; - private final CallService mCallService; - private final HardwareService mHardwareService; - private final AccountService mAccountService; - private final SystemInfoCallbacks mSystemInfoCallbacks; - - // references must be kept to avoid garbage collection while pointers are stored in the daemon. - private DaemonVideoCallback mHardwareCallback; - private DaemonPresenceCallback mPresenceCallback; - private DaemonCallAndConferenceCallback mCallAndConferenceCallback; - private DaemonConfigurationCallback mConfigurationCallback; - private DaemonDataTransferCallback mDataCallback; - private ConversationCallback mConversationCallback; - - private boolean mDaemonStarted = false; - - public DaemonService(SystemInfoCallbacks systemInfoCallbacks, ScheduledExecutorService executor, CallService callService, HardwareService hardwareService, AccountService accountService) { - mSystemInfoCallbacks = systemInfoCallbacks; - mExecutor = executor; - mCallService = callService; - mHardwareService = hardwareService; - mAccountService = accountService; - } - - public interface SystemInfoCallbacks { - void getHardwareAudioFormat(IntVect ret); - - void getAppDataPath(String name, StringVect ret); - - void getDeviceName(StringVect ret); - } - - public boolean isStarted() { - return mDaemonStarted; - } - - synchronized public void startDaemon() { - if (!mDaemonStarted) { - mDaemonStarted = true; - Log.i(TAG, "Starting daemon ..."); - mHardwareCallback = new DaemonVideoCallback(); - mPresenceCallback = new DaemonPresenceCallback(); - mCallAndConferenceCallback = new DaemonCallAndConferenceCallback(); - mConfigurationCallback = new DaemonConfigurationCallback(); - mDataCallback = new DaemonDataTransferCallback(); - mConversationCallback = new ConversationCallbackImpl(); - JamiService.init(mConfigurationCallback, mCallAndConferenceCallback, mPresenceCallback, mDataCallback, mHardwareCallback, mConversationCallback); - Log.i(TAG, "DaemonService started"); - } - } - - synchronized public void stopDaemon() { - mExecutor.shutdown(); - if (mDaemonStarted) { - Log.i(TAG, "stopping daemon ..."); - JamiService.fini(); - mDaemonStarted = false; - Log.i(TAG, "DaemonService stopped"); - } - } - - class DaemonConfigurationCallback extends ConfigurationCallback { - - @Override - public void volumeChanged(String device, int value) { - mAccountService.volumeChanged(device, value); - } - - @Override - public void accountsChanged() { - mExecutor.submit(() -> mAccountService.accountsChanged()); - } - - @Override - public void stunStatusFailure(String accountId) { - mAccountService.stunStatusFailure(accountId); - } - - @Override - public void registrationStateChanged(String accountId, String newState, int code, String detailString) { - mExecutor.submit(() -> mAccountService.registrationStateChanged(accountId, newState, code, detailString)); - } - - @Override - public void volatileAccountDetailsChanged(String account_id, StringMap details) { - Map<String, String> jdetails = details.toNative(); - mExecutor.submit(() -> mAccountService.volatileAccountDetailsChanged(account_id, jdetails)); - } - - @Override - public void accountDetailsChanged(String account_id, StringMap details) { - Map<String, String> jdetails = details.toNative(); - mExecutor.submit(() -> mAccountService.accountDetailsChanged(account_id, jdetails)); - } - - @Override - public void profileReceived(String accountId, String peerId, String path) { - mExecutor.submit(() -> mAccountService.profileReceived(accountId, peerId, path)); - } - - @Override - public void accountProfileReceived(String account_id, String name, String photo) { - mAccountService.accountProfileReceived(account_id, name, photo); - } - - @Override - public void incomingAccountMessage(String accountId, String from, String messageId, StringMap messages) { - if (messages == null || messages.isEmpty()) - return; - Map<String, String> jmessages = messages.toNativeFromUtf8(); - mExecutor.submit(() -> mAccountService.incomingAccountMessage(accountId, messageId, null, from, jmessages)); - } - - @Override - public void accountMessageStatusChanged(String accountId, String conversationId, String peer, String messageId, int status) { - mExecutor.submit(() -> mAccountService.accountMessageStatusChanged(accountId, conversationId, messageId, peer, status)); - } - - @Override - public void composingStatusChanged(String accountId, String conversationId, String contactUri, int status) { - mExecutor.submit(() -> mAccountService.composingStatusChanged(accountId, conversationId, contactUri, status)); - } - - @Override - public void errorAlert(int alert) { - mExecutor.submit(() -> mAccountService.errorAlert(alert)); - } - - @Override - public void getHardwareAudioFormat(IntVect ret) { - mSystemInfoCallbacks.getHardwareAudioFormat(ret); - } - - @Override - public void getAppDataPath(String name, StringVect ret) { - mSystemInfoCallbacks.getAppDataPath(name, ret); - } - - @Override - public void getDeviceName(StringVect ret) { - mSystemInfoCallbacks.getDeviceName(ret); - } - - @Override - public void knownDevicesChanged(String accountId, StringMap devices) { - Map<String, String> jdevices = devices.toNativeFromUtf8(); - mExecutor.submit(() -> mAccountService.knownDevicesChanged(accountId, jdevices)); - } - - @Override - public void exportOnRingEnded(String accountId, int code, String pin) { - mAccountService.exportOnRingEnded(accountId, code, pin); - } - - @Override - public void nameRegistrationEnded(String accountId, int state, String name) { - mAccountService.nameRegistrationEnded(accountId, state, name); - } - - @Override - public void registeredNameFound(String accountId, int state, String address, String name) { - mAccountService.registeredNameFound(accountId, state, address, name); - } - - @Override - public void userSearchEnded(String accountId, int state, String query, VectMap results) { - mAccountService.userSearchEnded(accountId, state, query, results.toNative()); - } - - @Override - public void migrationEnded(String accountId, String state) { - mAccountService.migrationEnded(accountId, state); - } - - @Override - public void deviceRevocationEnded(String accountId, String device, int state) { - mAccountService.deviceRevocationEnded(accountId, device, state); - } - - @Override - public void incomingTrustRequest(String accountId, String conversationId, String from, Blob message, long received) { - String jmessage = message.toJavaString(); - mExecutor.submit(() -> mAccountService.incomingTrustRequest(accountId, conversationId, from, jmessage, received)); - } - - @Override - public void contactAdded(String accountId, String uri, boolean confirmed) { - mExecutor.submit(() -> mAccountService.contactAdded(accountId, uri, confirmed)); - } - - @Override - public void contactRemoved(String accountId, String uri, boolean banned) { - mExecutor.submit(() -> mAccountService.contactRemoved(accountId, uri, banned)); - } - - @Override - public void messageSend(String message) { - mHardwareService.logMessage(message); - } - } - - class DaemonCallAndConferenceCallback extends Callback { - - @Override - public void callStateChanged(String callId, String newState, int detailCode) { - mCallService.callStateChanged(callId, newState, detailCode); - } - - @Override - public void incomingCall(String accountId, String callId, String from) { - mCallService.incomingCall(accountId, callId, from); - } - - @Override - public void connectionUpdate(String id, int state) { - mCallService.connectionUpdate(id, state); - } - - @Override - public void remoteRecordingChanged(String call_id, String peer_number, boolean state) { - mCallService.remoteRecordingChanged(call_id, Uri.fromString(peer_number), state); - } - - @Override - public void onConferenceInfosUpdated(String confId, VectMap infos) { - mCallService.onConferenceInfoUpdated(confId, infos.toNative()); - } - - @Override - public void incomingMessage(String callId, String from, StringMap messages) { - if (messages == null || messages.isEmpty()) - return; - Map<String, String> jmessages = messages.toNativeFromUtf8(); - mExecutor.submit(() -> mCallService.incomingMessage(callId, from, jmessages)); - } - - @Override - public void conferenceCreated(final String confId) { - mCallService.conferenceCreated(confId); - } - - @Override - public void conferenceRemoved(String confId) { - mCallService.conferenceRemoved(confId); - } - - @Override - public void conferenceChanged(String confId, String state) { - mCallService.conferenceChanged(confId, state); - } - - @Override - public void recordPlaybackFilepath(String id, String filename) { - mCallService.recordPlaybackFilepath(id, filename); - } - - @Override - public void onRtcpReportReceived(String callId, IntegerMap stats) { - mCallService.onRtcpReportReceived(callId); - } - } - - class DaemonPresenceCallback extends PresenceCallback { - - @Override - public void newServerSubscriptionRequest(String remote) { - Log.d(TAG, "newServerSubscriptionRequest: " + remote); - } - - @Override - public void serverError(String accountId, String error, String message) { - Log.d(TAG, "serverError: " + accountId + ", " + error + ", " + message); - } - - @Override - public void newBuddyNotification(String accountId, String buddyUri, int status, String lineStatus) { - mAccountService.getAccount(accountId).presenceUpdate(buddyUri, status == 1); - } - - @Override - public void subscriptionStateChanged(String accountId, String buddyUri, int state) { - Log.d(TAG, "subscriptionStateChanged: " + accountId + ", " + buddyUri + ", " + state); - } - } - - private class DaemonVideoCallback extends VideoCallback { - - @Override - public void decodingStarted(String id, String shmPath, int width, int height, boolean isMixer) { - mHardwareService.decodingStarted(id, shmPath, width, height, isMixer); - } - - @Override - public void decodingStopped(String id, String shmPath, boolean isMixer) { - mHardwareService.decodingStopped(id, shmPath, isMixer); - } - - @Override - public void getCameraInfo(String camId, IntVect formats, UintVect sizes, UintVect rates) { - mHardwareService.getCameraInfo(camId, formats, sizes, rates); - } - - @Override - public void setParameters(String camId, int format, int width, int height, int rate) { - mHardwareService.setParameters(camId, format, width, height, rate); - } - - @Override - public void requestKeyFrame() { - mHardwareService.requestKeyFrame(); - } - - @Override - public void setBitrate(String device, int bitrate) { - mHardwareService.setBitrate(device, bitrate); - } - - @Override - public void startCapture(String camId) { - Log.d(TAG, "startCapture: " + camId); - mHardwareService.startCapture(camId); - } - - @Override - public void stopCapture() { - mHardwareService.stopCapture(); - } - } - - class DaemonDataTransferCallback extends DataTransferCallback { - @Override - public void dataTransferEvent(String accountId, String conversationId, String interactionId, String fileId, int eventCode) { - Log.d(TAG, "dataTransferEvent: conversationId=" + conversationId + ", fileId=" + fileId + ", eventCode=" + eventCode); - mAccountService.dataTransferEvent(accountId, conversationId, interactionId, fileId, eventCode); - } - } - - class ConversationCallbackImpl extends ConversationCallback { - @Override - public void conversationLoaded(long id, String accountId, String conversationId, VectMap messages) { - mAccountService.conversationLoaded(accountId, conversationId, messages.toNative()); - } - - @Override - public void conversationReady(String accountId, String conversationId) { - mAccountService.conversationReady(accountId, conversationId); - } - - @Override - public void conversationRemoved(String accountId, String conversationId) { - mAccountService.conversationRemoved(accountId, conversationId); - } - - @Override - public void conversationRequestReceived(String accountId, String conversationId, StringMap metadata) { - mAccountService.conversationRequestReceived(accountId, conversationId, metadata.toNative()); - } - - @Override - public void conversationRequestDeclined(String accountId, String conversationId) { - mAccountService.conversationRequestDeclined(accountId, conversationId); - } - @Override - public void conversationMemberEvent(String accountId, String conversationId, String uri, int event) { - mAccountService.conversationMemberEvent(accountId, conversationId, uri, event); - } - - @Override - public void messageReceived(String accountId, String conversationId, StringMap message) { - mAccountService.messageReceived(accountId, conversationId, message.toNative()); - } - } - -} diff --git a/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.kt b/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.kt new file mode 100644 index 0000000000000000000000000000000000000000..7ff7f380c70d877c45217ad3502c1adb448472e5 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.kt @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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 <https://www.gnu.org/licenses/>. + */ +package net.jami.services + +import net.jami.daemon.* +import net.jami.model.Uri +import net.jami.utils.Log +import java.util.concurrent.ScheduledExecutorService +import javax.inject.Named + +class DaemonService( + private val mSystemInfoCallbacks: SystemInfoCallbacks, + @Named("DaemonExecutor") private val mExecutor: ScheduledExecutorService, + private val mCallService: CallService, + private val mHardwareService: HardwareService, + private val mAccountService: AccountService +) { + // references must be kept to avoid garbage collection while pointers are stored in the daemon. + private var mHardwareCallback: DaemonVideoCallback? = null + private var mPresenceCallback: DaemonPresenceCallback? = null + private var mCallAndConferenceCallback: DaemonCallAndConferenceCallback? = null + private var mConfigurationCallback: DaemonConfigurationCallback? = null + private var mDataCallback: DaemonDataTransferCallback? = null + private var mConversationCallback: ConversationCallback? = null + var isStarted = false + private set + + interface SystemInfoCallbacks { + fun getHardwareAudioFormat(ret: IntVect) + fun getAppDataPath(name: String, ret: StringVect) + fun getDeviceName(ret: StringVect) + } + + @Synchronized + fun startDaemon() { + if (!isStarted) { + isStarted = true + Log.i(TAG, "Starting daemon ...") + mHardwareCallback = DaemonVideoCallback() + mPresenceCallback = DaemonPresenceCallback() + mCallAndConferenceCallback = DaemonCallAndConferenceCallback() + mConfigurationCallback = DaemonConfigurationCallback() + mDataCallback = DaemonDataTransferCallback() + mConversationCallback = ConversationCallbackImpl() + JamiService.init( + mConfigurationCallback, + mCallAndConferenceCallback, + mPresenceCallback, + mDataCallback, + mHardwareCallback, + mConversationCallback + ) + Log.i(TAG, "DaemonService started") + } + } + + @Synchronized + fun stopDaemon() { + mExecutor.shutdown() + if (isStarted) { + Log.i(TAG, "stopping daemon ...") + JamiService.fini() + isStarted = false + Log.i(TAG, "DaemonService stopped") + } + } + + internal inner class DaemonConfigurationCallback : ConfigurationCallback() { + override fun volumeChanged(device: String, value: Int) { + mAccountService.volumeChanged(device, value) + } + + override fun accountsChanged() { + mExecutor.submit { mAccountService.accountsChanged() } + } + + override fun stunStatusFailure(accountId: String) { + mAccountService.stunStatusFailure(accountId) + } + + override fun registrationStateChanged(accountId: String, newState: String, code: Int, detailString: String) { + mExecutor.submit { mAccountService.registrationStateChanged(accountId, newState, code, detailString) } + } + + override fun volatileAccountDetailsChanged(account_id: String, details: StringMap) { + val jdetails: Map<String, String> = details.toNative() + mExecutor.submit { mAccountService.volatileAccountDetailsChanged(account_id, jdetails) } + } + + override fun accountDetailsChanged(account_id: String, details: StringMap) { + val jdetails: Map<String, String> = details.toNative() + mExecutor.submit { mAccountService.accountDetailsChanged(account_id, jdetails) } + } + + override fun profileReceived(accountId: String, peerId: String, path: String) { + mExecutor.submit { mAccountService.profileReceived(accountId, peerId, path) } + } + + override fun accountProfileReceived(account_id: String, name: String, photo: String) { + mAccountService.accountProfileReceived(account_id, name, photo) + } + + override fun incomingAccountMessage(accountId: String, from: String, messageId: String, messages: StringMap) { + if (messages.isEmpty()) return + val jmessages: Map<String, String> = messages.toNativeFromUtf8() + mExecutor.submit { mAccountService.incomingAccountMessage(accountId, messageId, null, from, jmessages) } + } + + override fun accountMessageStatusChanged(accountId: String, conversationId: String, peer: String, messageId: String, status: Int) { + mExecutor.submit { + mAccountService.accountMessageStatusChanged(accountId, conversationId, messageId, peer, status) + } + } + + override fun composingStatusChanged(accountId: String, conversationId: String, contactUri: String, status: Int) { + mExecutor.submit { mAccountService.composingStatusChanged(accountId, conversationId, contactUri, status) } + } + + override fun errorAlert(alert: Int) { + mExecutor.submit { mAccountService.errorAlert(alert) } + } + + override fun getHardwareAudioFormat(ret: IntVect) { + mSystemInfoCallbacks.getHardwareAudioFormat(ret) + } + + override fun getAppDataPath(name: String, ret: StringVect) { + mSystemInfoCallbacks.getAppDataPath(name, ret) + } + + override fun getDeviceName(ret: StringVect) { + mSystemInfoCallbacks.getDeviceName(ret) + } + + override fun knownDevicesChanged(accountId: String, devices: StringMap) { + val jdevices: Map<String, String> = devices.toNativeFromUtf8() + mExecutor.submit { mAccountService.knownDevicesChanged(accountId, jdevices) } + } + + override fun exportOnRingEnded(accountId: String, code: Int, pin: String) { + mAccountService.exportOnRingEnded(accountId, code, pin) + } + + override fun nameRegistrationEnded(accountId: String, state: Int, name: String) { + mAccountService.nameRegistrationEnded(accountId, state, name) + } + + override fun registeredNameFound(accountId: String, state: Int, address: String, name: String) { + mAccountService.registeredNameFound(accountId, state, address, name) + } + + override fun userSearchEnded(accountId: String, state: Int, query: String, results: VectMap) { + mAccountService.userSearchEnded(accountId, state, query, results.toNative()) + } + + override fun migrationEnded(accountId: String, state: String) { + mAccountService.migrationEnded(accountId, state) + } + + override fun deviceRevocationEnded(accountId: String, device: String, state: Int) { + mAccountService.deviceRevocationEnded(accountId, device, state) + } + + override fun incomingTrustRequest(accountId: String, conversationId: String, from: String, message: Blob, received: Long) { + val jmessage = message.toJavaString() + mExecutor.submit { + mAccountService.incomingTrustRequest(accountId, conversationId, from, jmessage, received) + } + } + + override fun contactAdded(accountId: String, uri: String, confirmed: Boolean) { + mExecutor.submit { mAccountService.contactAdded(accountId, uri, confirmed) } + } + + override fun contactRemoved(accountId: String, uri: String, banned: Boolean) { + mExecutor.submit { mAccountService.contactRemoved(accountId, uri, banned) } + } + + override fun messageSend(message: String) { + mHardwareService.logMessage(message) + } + } + + internal inner class DaemonCallAndConferenceCallback : Callback() { + override fun callStateChanged(callId: String, newState: String, detailCode: Int) { + mCallService.callStateChanged(callId, newState, detailCode) + } + + override fun incomingCall(accountId: String, callId: String, from: String) { + mCallService.incomingCall(accountId, callId, from) + } + + override fun connectionUpdate(id: String, state: Int) { + mCallService.connectionUpdate(id, state) + } + + override fun remoteRecordingChanged(call_id: String, peer_number: String, state: Boolean) { + mCallService.remoteRecordingChanged(call_id, Uri.fromString(peer_number), state) + } + + override fun onConferenceInfosUpdated(confId: String, infos: VectMap) { + mCallService.onConferenceInfoUpdated(confId, infos.toNative()) + } + + override fun incomingMessage(callId: String, from: String, messages: StringMap) { + if (messages.isEmpty()) return + val jmessages: Map<String, String> = messages.toNativeFromUtf8() + mExecutor.submit { mCallService.incomingMessage(callId, from, jmessages) } + } + + override fun conferenceCreated(confId: String) { + mCallService.conferenceCreated(confId) + } + + override fun conferenceRemoved(confId: String) { + mCallService.conferenceRemoved(confId) + } + + override fun conferenceChanged(confId: String, state: String) { + mCallService.conferenceChanged(confId, state) + } + + override fun recordPlaybackFilepath(id: String, filename: String) { + mCallService.recordPlaybackFilepath(id, filename) + } + + override fun onRtcpReportReceived(callId: String, stats: IntegerMap) { + mCallService.onRtcpReportReceived(callId) + } + } + + internal inner class DaemonPresenceCallback : PresenceCallback() { + override fun newServerSubscriptionRequest(remote: String) { + Log.d(TAG, "newServerSubscriptionRequest: $remote") + } + + override fun serverError(accountId: String, error: String, message: String) { + Log.d(TAG, "serverError: $accountId, $error, $message") + } + + override fun newBuddyNotification(accountId: String, buddyUri: String, status: Int, lineStatus: String) { + mAccountService.getAccount(accountId)!!.presenceUpdate(buddyUri, status == 1) + } + + override fun subscriptionStateChanged(accountId: String, buddyUri: String, state: Int) { + Log.d(TAG, "subscriptionStateChanged: $accountId, $buddyUri, $state") + } + } + + private inner class DaemonVideoCallback : VideoCallback() { + override fun decodingStarted(id: String, shmPath: String, width: Int, height: Int, isMixer: Boolean) { + mHardwareService.decodingStarted(id, shmPath, width, height, isMixer) + } + + override fun decodingStopped(id: String, shmPath: String, isMixer: Boolean) { + mHardwareService.decodingStopped(id, shmPath, isMixer) + } + + override fun getCameraInfo(camId: String, formats: IntVect, sizes: UintVect, rates: UintVect) { + mHardwareService.getCameraInfo(camId, formats, sizes, rates) + } + + override fun setParameters(camId: String, format: Int, width: Int, height: Int, rate: Int) { + mHardwareService.setParameters(camId, format, width, height, rate) + } + + override fun requestKeyFrame() { + mHardwareService.requestKeyFrame() + } + + override fun setBitrate(device: String, bitrate: Int) { + mHardwareService.setBitrate(device, bitrate) + } + + override fun startCapture(camId: String) { + Log.d(TAG, "startCapture: $camId") + mHardwareService.startCapture(camId) + } + + override fun stopCapture() { + mHardwareService.stopCapture() + } + } + + internal inner class DaemonDataTransferCallback : DataTransferCallback() { + override fun dataTransferEvent(accountId: String, conversationId: String, interactionId: String, fileId: String, eventCode: Int) { + Log.d(TAG, "dataTransferEvent: conversationId=$conversationId, fileId=$fileId, eventCode=$eventCode") + mAccountService.dataTransferEvent(accountId, conversationId, interactionId, fileId, eventCode) + } + } + + internal inner class ConversationCallbackImpl : ConversationCallback() { + override fun conversationLoaded(id: Long, accountId: String, conversationId: String, messages: VectMap) { + mAccountService.conversationLoaded(accountId, conversationId, messages.toNative()) + } + + override fun conversationReady(accountId: String, conversationId: String) { + mAccountService.conversationReady(accountId, conversationId) + } + + override fun conversationRemoved(accountId: String, conversationId: String) { + mAccountService.conversationRemoved(accountId, conversationId) + } + + override fun conversationRequestReceived(accountId: String, conversationId: String, metadata: StringMap) { + mAccountService.conversationRequestReceived(accountId, conversationId, metadata.toNative()) + } + + override fun conversationRequestDeclined(accountId: String, conversationId: String) { + mAccountService.conversationRequestDeclined(accountId, conversationId) + } + + override fun conversationMemberEvent(accountId: String, conversationId: String, uri: String, event: Int) { + mAccountService.conversationMemberEvent(accountId, conversationId, uri, event) + } + + override fun messageReceived(accountId: String, conversationId: String, message: StringMap) { + mAccountService.messageReceived(accountId, conversationId, message.toNative()) + } + } + + companion object { + private val TAG = DaemonService::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/services/DeviceRuntimeService.kt b/ring-android/libringclient/src/main/java/net/jami/services/DeviceRuntimeService.kt index 38ab0eb9c97028bb56d8d17b2b68a5eeb7820cee..2c6da03b8f6415092e1464585e1fb4bdc2955289 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/DeviceRuntimeService.kt +++ b/ring-android/libringclient/src/main/java/net/jami/services/DeviceRuntimeService.kt @@ -31,10 +31,9 @@ abstract class DeviceRuntimeService : SystemInfoCallbacks { abstract fun getConversationPath(conversationId: String, name: String): File abstract fun getConversationPath(accountId: String, conversationId: String, name: String): File fun getConversationPath(interaction: DataTransfer): File { - return if (interaction.conversationId == null) getConversationPath( - interaction.conversation!!.participant, - interaction.storagePath - ) else interaction.publicPath!! + return if (interaction.conversationId == null) + getConversationPath(interaction.conversation!!.participant!!, interaction.storagePath) + else interaction.publicPath!! } fun getNewConversationPath(accountId: String, conversationId: String, name: String): File { diff --git a/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.kt b/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.kt index 91239b566f8f37c3e7981739188f2ec264d585b0..7345ec96fe8bbfd028a2bfffea79291d96f84ae4 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.kt +++ b/ring-android/libringclient/src/main/java/net/jami/services/HardwareService.kt @@ -26,7 +26,6 @@ import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.subjects.BehaviorSubject import io.reactivex.rxjava3.subjects.PublishSubject import io.reactivex.rxjava3.subjects.Subject -import net.jami.utils.StringUtils.isEmpty import net.jami.model.Call.CallStatus import net.jami.daemon.IntVect import net.jami.daemon.UintVect @@ -53,33 +52,19 @@ abstract class HardwareService( var callId: String? = null } - class BluetoothEvent { - var connected = false - } + class BluetoothEvent (val connected: Boolean) enum class AudioOutput { INTERNAL, SPEAKERS, BLUETOOTH } - class AudioState { - val outputType: AudioOutput - val outputName: String? - - constructor(ot: AudioOutput) { - outputType = ot - outputName = null - } - - constructor(ot: AudioOutput, name: String?) { - outputType = ot - outputName = name - } - } + class AudioState(val outputType: AudioOutput, val outputName: String? = null) protected val videoEvents: Subject<VideoEvent> = PublishSubject.create() protected val bluetoothEvents: Subject<BluetoothEvent> = PublishSubject.create() protected val audioStateSubject: Subject<AudioState> = BehaviorSubject.createDefault(STATE_INTERNAL) protected val connectivityEvents: Subject<Boolean> = BehaviorSubject.create() + fun getVideoEvents(): Observable<VideoEvent> { return videoEvents } @@ -114,18 +99,18 @@ abstract class HardwareService( abstract fun endCapture() abstract fun stopScreenShare() abstract fun requestKeyFrame() - abstract fun setBitrate(device: String?, bitrate: Int) - abstract fun addVideoSurface(id: String?, holder: Any?) - abstract fun updateVideoSurfaceId(currentId: String?, newId: String?) - abstract fun removeVideoSurface(id: String?) - abstract fun addPreviewVideoSurface(holder: Any?, conference: Conference?) - abstract fun updatePreviewVideoSurface(conference: Conference?) + abstract fun setBitrate(device: String, bitrate: Int) + abstract fun addVideoSurface(id: String, holder: Any) + abstract fun updateVideoSurfaceId(currentId: String, newId: String) + abstract fun removeVideoSurface(id: String) + abstract fun addPreviewVideoSurface(holder: Any, conference: Conference?) + abstract fun updatePreviewVideoSurface(conference: Conference) abstract fun removePreviewVideoSurface() - abstract fun switchInput(id: String?, setDefaultCamera: Boolean) + abstract fun switchInput(id: String, setDefaultCamera: Boolean) abstract fun setPreviewSettings() abstract fun hasCamera(): Boolean abstract val cameraCount: Int - abstract val maxResolutions: Observable<Tuple<Int, Int>> + abstract val maxResolutions: Observable<Tuple<Int?, Int?>> abstract val isPreviewFromFrontCamera: Boolean abstract fun shouldPlaySpeaker(): Boolean abstract fun unregisterCameraDetectionCallback() @@ -137,12 +122,12 @@ abstract class HardwareService( mExecutor.execute { JamiService.connectivityChanged() } } - protected fun switchInput(id: String?, uri: String) { + protected fun switchInput(id: String, uri: String) { Log.i(TAG, "switchInput() $uri") mExecutor.execute { JamiService.switchInput(id, uri) } } - fun setPreviewSettings(cameraMaps: Map<String?, StringMap?>) { + fun setPreviewSettings(cameraMaps: Map<String, StringMap>) { mExecutor.execute { Log.i(TAG, "applySettings() thread running...") for ((key, value) in cameraMaps) { @@ -151,7 +136,7 @@ abstract class HardwareService( } } - fun startVideo(inputId: String?, surface: Any?, width: Int, height: Int): Long { + fun startVideo(inputId: String, surface: Any, width: Int, height: Int): Long { val inputWindow = JamiService.acquireNativeWindow(surface) if (inputWindow == 0L) { return inputWindow @@ -161,7 +146,7 @@ abstract class HardwareService( return inputWindow } - fun stopVideo(inputId: String?, inputWindow: Long) { + fun stopVideo(inputId: String, inputWindow: Long) { if (inputWindow == 0L) { return } @@ -170,7 +155,7 @@ abstract class HardwareService( } abstract fun setDeviceOrientation(rotation: Int) - protected abstract val videoDevices: List<String?>? + protected abstract val videoDevices: List<String> private var logs: Observable<String>? = null private var logEmitter: Emitter<String>? = null diff --git a/ring-android/libringclient/src/main/java/net/jami/services/HistoryService.kt b/ring-android/libringclient/src/main/java/net/jami/services/HistoryService.kt index 99a33285d345d0eaf2535095bdd042c6f4fbf3bb..d207768b903d9d390bf04258bfdcdaf5a78f49c4 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/HistoryService.kt +++ b/ring-android/libringclient/src/main/java/net/jami/services/HistoryService.kt @@ -117,12 +117,12 @@ abstract class HistoryService { Log.d(TAG, "Inserting interaction for account -> $accountId") val conversationDataDao = getConversationDataDao(accountId) val history = conversationDataDao.queryBuilder().where().eq(ConversationHistory.COLUMN_PARTICIPANT, conversation.participant).queryForFirst() ?: - conversationDataDao.createIfNotExists(ConversationHistory(conversation.participant))!! + conversationDataDao.createIfNotExists(ConversationHistory(conversation.participant!!))!! //interaction.setConversation(conversation); conversation.id = history.id getInteractionDataDao(accountId).create(interaction) } - .doOnError { e: Throwable? -> Log.e(TAG, "Can't insert interaction", e) } + .doOnError { e: Throwable -> Log.e(TAG, "Can't insert interaction", e) } .subscribeOn(scheduler) } @@ -177,8 +177,8 @@ abstract class HistoryService { .orderBy(Interaction.COLUMN_TIMESTAMP, true) .where().eq(Interaction.COLUMN_CONVERSATION, conversationId) .prepare()) - }.subscribeOn(scheduler).doOnError { e: Throwable? -> Log.e(TAG, "Can't load conversation from database", e) } - .onErrorReturn { e: Throwable? -> ArrayList() } + }.subscribeOn(scheduler).doOnError { e: Throwable -> Log.e(TAG, "Can't load conversation from database", e) } + .onErrorReturn { ArrayList() } } fun incomingMessage(accountId: String, daemonId: String?, from: String, message: String): Single<TextMessage> { diff --git a/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.java b/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.java deleted file mode 100644 index 7239c8d08150b6f31b65715137fa358ef343e64b..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.services; - -import net.jami.model.Account; -import net.jami.model.Contact; -import net.jami.model.Conference; -import net.jami.model.Conversation; -import net.jami.model.DataTransfer; -import net.jami.model.Call; -import net.jami.model.Uri; - -public interface NotificationService { - String TRUST_REQUEST_NOTIFICATION_ACCOUNT_ID = "trustRequestNotificationAccountId"; - String TRUST_REQUEST_NOTIFICATION_FROM = "trustRequestNotificationFrom"; - String KEY_CALL_ID = "callId"; - String KEY_HOLD_ID = "holdId"; - String KEY_END_ID = "endId"; - String KEY_NOTIFICATION_ID = "notificationId"; - - Object showCallNotification(int callId); - void cancelCallNotification(); - void handleCallNotification(Conference conference, boolean remove); - void showMissedCallNotification(Call call); - - void showTextNotification(String accountId, Conversation conversation); - void cancelTextNotification(String accountId, Uri contact); - - void cancelAll(); - - void showIncomingTrustRequestNotification(Account account); - void cancelTrustRequestNotification(String accountID); - - void showFileTransferNotification(Conversation conversation, DataTransfer info); - void cancelFileNotification(int id, boolean isMigratingToService); - void handleDataTransferNotification(DataTransfer transfer, Conversation contact, boolean remove); - void removeTransferNotification(String accountId, Uri conversationUri, String fileId); - Object getDataTransferNotification(int notificationId); - - //void updateNotification(Object notification, int notificationId); - - Object getServiceNotification(); - - void onConnectionUpdate(Boolean b); - - void showLocationNotification(Account first, Contact contact); - void cancelLocationNotification(Account first, Contact contact); - -} diff --git a/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.kt b/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.kt new file mode 100644 index 0000000000000000000000000000000000000000..1270afbef6b2d19209a61b86a3c1a1ec195c50ed --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Aline Bonnet <aline.bonnet@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.services + +import net.jami.model.* + +interface NotificationService { + fun showCallNotification(callId: Int): Any? + fun cancelCallNotification() + fun handleCallNotification(conference: Conference, remove: Boolean) + fun showMissedCallNotification(call: Call) + fun showTextNotification(accountId: String, conversation: Conversation) + fun cancelTextNotification(accountId: String, contact: Uri) + fun cancelAll() + fun showIncomingTrustRequestNotification(account: Account) + fun cancelTrustRequestNotification(accountID: String) + fun showFileTransferNotification(conversation: Conversation, info: DataTransfer) + fun cancelFileNotification(id: Int, isMigratingToService: Boolean) + fun handleDataTransferNotification(transfer: DataTransfer, contact: Conversation, remove: Boolean) + fun removeTransferNotification(accountId: String, conversationUri: Uri, fileId: String) + fun getDataTransferNotification(notificationId: Int): Any + + //void updateNotification(Object notification, int notificationId); + val serviceNotification: Any + fun onConnectionUpdate(b: Boolean) + fun showLocationNotification(first: Account, contact: Contact) + fun cancelLocationNotification(first: Account, contact: Contact) + + companion object { + const val TRUST_REQUEST_NOTIFICATION_ACCOUNT_ID = "trustRequestNotificationAccountId" + const val TRUST_REQUEST_NOTIFICATION_FROM = "trustRequestNotificationFrom" + const val KEY_CALL_ID = "callId" + const val KEY_HOLD_ID = "holdId" + const val KEY_END_ID = "endId" + const val KEY_NOTIFICATION_ID = "notificationId" + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/services/PreferencesService.java b/ring-android/libringclient/src/main/java/net/jami/services/PreferencesService.java deleted file mode 100644 index 2062fdf328e80a469a3b8c78a39b982acb6601f0..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/services/PreferencesService.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.services; - -import java.util.Set; - -import javax.inject.Inject; - -import net.jami.model.Settings; - -import io.reactivex.rxjava3.core.Observable; -import io.reactivex.rxjava3.subjects.BehaviorSubject; -import io.reactivex.rxjava3.subjects.Subject; - -public abstract class PreferencesService { - - private final AccountService mAccountService; - private final DeviceRuntimeService mDeviceService; - - public PreferencesService(AccountService accountService, DeviceRuntimeService deviceService) { - mAccountService = accountService; - mDeviceService = deviceService; - } - - private Settings mUserSettings; - private final Subject<Settings> mSettingsSubject = BehaviorSubject.create(); - - protected abstract Settings loadSettings(); - protected abstract void saveSettings(Settings settings); - - public Settings getSettings() { - if (mUserSettings == null) { - mUserSettings = loadSettings(); - mSettingsSubject.onNext(mUserSettings); - } - return new Settings(mUserSettings); - } - - public void setSettings(Settings settings) { - saveSettings(settings); - boolean allowPush = settings.isAllowPushNotifications(); - if (mUserSettings == null || mUserSettings.isAllowPushNotifications() != allowPush) { - mAccountService.setPushNotificationToken(allowPush ? mDeviceService.getPushToken() : ""); - mAccountService.setProxyEnabled(allowPush); - } - mUserSettings = settings; - mSettingsSubject.onNext(settings); - } - - protected Settings getUserSettings() { - return mUserSettings; - } - - public Observable<Settings> getSettingsSubject() { - return mSettingsSubject; - } - - public abstract boolean hasNetworkConnected(); - - public abstract boolean isPushAllowed(); - - public abstract void saveRequestPreferences(String accountId, String contactId); - - public abstract Set<String> loadRequestsPreferences(String accountId); - - public abstract void removeRequestPreferences(String accountId, String contactId); - - public abstract int getResolution(); - - public abstract int getBitrate(); - - public abstract boolean isHardwareAccelerationEnabled(); - - - public abstract void setDarkMode(boolean enabled); - - public abstract boolean getDarkMode(); - - public abstract void loadDarkMode(); - - public abstract int getMaxFileAutoAccept(String accountId); -} diff --git a/ring-android/libringclient/src/main/java/net/jami/services/PreferencesService.kt b/ring-android/libringclient/src/main/java/net/jami/services/PreferencesService.kt new file mode 100644 index 0000000000000000000000000000000000000000..2bb700694aa04b4f922d1e5fdb6ba8c2aa09a481 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/services/PreferencesService.kt @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.services + +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.subjects.BehaviorSubject +import io.reactivex.rxjava3.subjects.Subject +import net.jami.model.Settings + +abstract class PreferencesService( + private val mAccountService: AccountService, + private val mDeviceService: DeviceRuntimeService +) { + protected var userSettings: Settings? = null + private set + private val mSettingsSubject: Subject<Settings> = BehaviorSubject.create() + protected abstract fun loadSettings(): Settings + protected abstract fun saveSettings(settings: Settings) + var settings: Settings + get() { + if (userSettings == null) { + val newSettings = loadSettings() + userSettings = newSettings + mSettingsSubject.onNext(newSettings) + } + return Settings(userSettings) + } + set(settings) { + saveSettings(settings) + val allowPush = settings.isAllowPushNotifications + if (userSettings == null || userSettings!!.isAllowPushNotifications != allowPush) { + mAccountService.setPushNotificationToken(if (allowPush) mDeviceService.pushToken else "") + mAccountService.setProxyEnabled(allowPush) + } + userSettings = settings + mSettingsSubject.onNext(settings) + } + val settingsSubject: Observable<Settings> + get() = mSettingsSubject + + abstract fun hasNetworkConnected(): Boolean + abstract val isPushAllowed: Boolean + abstract fun saveRequestPreferences(accountId: String, contactId: String) + abstract fun loadRequestsPreferences(accountId: String): Set<String> + abstract fun removeRequestPreferences(accountId: String, contactId: String) + abstract val resolution: Int + abstract val bitrate: Int + abstract val isHardwareAccelerationEnabled: Boolean + abstract var darkMode: Boolean + abstract fun loadDarkMode() + abstract fun getMaxFileAutoAccept(accountId: String): Int +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/services/VCardService.java b/ring-android/libringclient/src/main/java/net/jami/services/VCardService.java deleted file mode 100644 index 87631ba8b75e45fa4f67e0ef1ca5c12cc7d9d8f4..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/services/VCardService.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.services; - -import java.io.File; - -import net.jami.model.Account; -import net.jami.utils.Tuple; -import ezvcard.VCard; -import io.reactivex.rxjava3.core.Maybe; -import io.reactivex.rxjava3.core.Observable; -import io.reactivex.rxjava3.core.Single; - -public abstract class VCardService { - - public static final int MAX_SIZE_SIP = 256 * 1024; - public static final int MAX_SIZE_REQUEST = 16 * 1024; - - public abstract Observable<Tuple<String, Object>> loadProfile(Account account); - - public abstract Maybe<VCard> loadSmallVCard(String accountId, int maxSize); - public Single<VCard> loadSmallVCardWithDefault(String accountId, int maxSize) { - return loadSmallVCard(accountId, maxSize) - .switchIfEmpty(Single.fromCallable(VCard::new)); - } - - public abstract Single<VCard> saveVCardProfile(String accountId, String uri, String displayName, String picture); - public abstract Single<Tuple<String, Object>> loadVCardProfile(VCard vcard); - public abstract Single<Tuple<String, Object>> peerProfileReceived(String accountId, String peerId, File vcard); - - public abstract Object base64ToBitmap(String base64); - -} diff --git a/ring-android/libringclient/src/main/java/net/jami/services/VCardService.kt b/ring-android/libringclient/src/main/java/net/jami/services/VCardService.kt new file mode 100644 index 0000000000000000000000000000000000000000..619b5a964580dc4fe093f9d25ab07af1a4d40651 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/services/VCardService.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.services + +import ezvcard.VCard +import io.reactivex.rxjava3.core.Maybe +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single +import net.jami.model.Account +import net.jami.model.Profile +import java.io.File + +abstract class VCardService { + abstract fun loadProfile(account: Account): Observable<Profile> + abstract fun loadSmallVCard(accountId: String, maxSize: Int): Maybe<VCard> + fun loadSmallVCardWithDefault(accountId: String, maxSize: Int): Single<VCard> { + return loadSmallVCard(accountId, maxSize) + .switchIfEmpty(Single.fromCallable { VCard() }) + } + + abstract fun saveVCardProfile(accountId: String, uri: String?, displayName: String?, picture: String?): Single<VCard> + + abstract fun loadVCardProfile(vcard: VCard): Single<Profile> + abstract fun peerProfileReceived(accountId: String, peerId: String, vcard: File): Single<Profile> + abstract fun accountProfileReceived(accountId: String, vcardFile: File): Single<Profile> + abstract fun base64ToBitmap(base64: String?): Any? + + companion object { + const val MAX_SIZE_SIP = 256 * 1024 + const val MAX_SIZE_REQUEST = 16 * 1024 + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/settings/AdvancedAccountPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/settings/AdvancedAccountPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..0a8b622a644f6dd5a0ffa89a1be531adde0110f6 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/settings/AdvancedAccountPresenter.kt @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.settings + +import net.jami.model.Account +import net.jami.model.ConfigKey +import net.jami.mvp.RootPresenter +import net.jami.services.AccountService +import net.jami.services.ConversationFacade +import net.jami.utils.Log +import java.net.NetworkInterface +import java.net.SocketException +import java.util.* +import javax.inject.Inject + +class AdvancedAccountPresenter @Inject constructor( + private var mConversationFacade: ConversationFacade, + private var mAccountService: AccountService +) : RootPresenter<AdvancedAccountView>() { + private var mAccount: Account? = null + fun init(accountId: String?) { + mAccount = mAccountService.getAccount(accountId) + if (mAccount != null) { + view!!.initView(mAccount!!.config, networkInterfaces) + } + } + + fun twoStatePreferenceChanged(configKey: ConfigKey?, newValue: Any) { + mAccount!!.setDetail(configKey!!, newValue.toString()) + updateAccount() + } + + fun passwordPreferenceChanged(configKey: ConfigKey?, newValue: Any) { + mAccount!!.setDetail(configKey!!, newValue.toString()) + updateAccount() + } + + fun preferenceChanged(configKey: ConfigKey, newValue: Any) { + var newValue = newValue + if (configKey === ConfigKey.AUDIO_PORT_MAX || configKey === ConfigKey.AUDIO_PORT_MIN) { + newValue = adjustRtpRange(Integer.valueOf(newValue as String)) + } + mAccount!!.setDetail(configKey, newValue.toString()) + updateAccount() + } + + private fun updateAccount() { + mAccountService.setCredentials(mAccount!!.accountID, mAccount!!.credentialsHashMapList) + mAccountService.setAccountDetails(mAccount!!.accountID, mAccount!!.details) + } + + private fun adjustRtpRange(newValue: Int): String { + if (newValue < 1024) { + return "1024" + } + return if (newValue > 65534) "65534" else newValue.toString() + } + + private val networkInterfaces: ArrayList<CharSequence> + get() { + val result = ArrayList<CharSequence>() + result.add("default") + try { + val list = NetworkInterface.getNetworkInterfaces() + while (list.hasMoreElements()) { + val i = list.nextElement() + if (i.isUp) { + result.add(i.displayName) + } + } + } catch (e: SocketException) { + Log.e(TAG, "Error enumerating interfaces: ", e) + } + return result + } + + companion object { + val TAG = AdvancedAccountPresenter::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/account/HomeAccountCreationView.java b/ring-android/libringclient/src/main/java/net/jami/settings/AdvancedAccountView.kt similarity index 80% rename from ring-android/libringclient/src/main/java/net/jami/account/HomeAccountCreationView.java rename to ring-android/libringclient/src/main/java/net/jami/settings/AdvancedAccountView.kt index 6ff690b51a106d7258d1482b5870e902e6a1ae28..51b387b149b1ae677b87036297a798fd79a39052 100644 --- a/ring-android/libringclient/src/main/java/net/jami/account/HomeAccountCreationView.java +++ b/ring-android/libringclient/src/main/java/net/jami/settings/AdvancedAccountView.kt @@ -17,14 +17,11 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +package net.jami.settings -package net.jami.account; +import net.jami.model.AccountConfig +import java.util.* -public interface HomeAccountCreationView { - - void goToAccountCreation(); - - void goToAccountLink(); - - void goToAccountConnect(); -} +interface AdvancedAccountView { + fun initView(config: AccountConfig, networkInterfaces: ArrayList<CharSequence>) +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/settings/GeneralAccountPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/settings/GeneralAccountPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..620e6833a7747f2adbeac7b7c42245a4b03c0836 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/settings/GeneralAccountPresenter.kt @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.settings + +import io.reactivex.rxjava3.core.Scheduler +import net.jami.model.Account +import net.jami.model.ConfigKey +import net.jami.mvp.RootPresenter +import net.jami.services.AccountService +import net.jami.services.HardwareService +import net.jami.services.PreferencesService +import net.jami.utils.Log +import net.jami.utils.Tuple +import javax.inject.Inject +import javax.inject.Named + +class GeneralAccountPresenter @Inject internal constructor( + private val mAccountService: AccountService, + private val mHardwareService: HardwareService, + private val mPreferenceService: PreferencesService, + @param:Named("UiScheduler") private val mUiScheduler: Scheduler +) : RootPresenter<GeneralAccountView>() { + private var mAccount: Account? = null + + // Init with current account + fun init() { + init(mAccountService.currentAccount) + } + + fun init(accountId: String?) { + init(mAccountService.getAccount(accountId)) + } + + private fun init(account: Account?) { + mCompositeDisposable.clear() + mAccount = account + if (account != null) { + if (account.isJami) { + view!!.addJamiPreferences(account.accountID) + } else { + view!!.addSipPreferences() + } + view!!.accountChanged(account) + mCompositeDisposable.add(mAccountService.getObservableAccount(account.accountID) + .observeOn(mUiScheduler) + .subscribe { acc: Account -> view!!.accountChanged(acc) }) + mCompositeDisposable.add( + mHardwareService.maxResolutions + .observeOn(mUiScheduler) + .subscribe({ res: Tuple<Int?, Int?> -> + if (res.first == null) { + view?.updateResolutions(null, mPreferenceService.resolution) + } else { + view?.updateResolutions(res, mPreferenceService.resolution) + } + }, + { view?.updateResolutions(null, mPreferenceService.resolution) }) + ) + } else { + Log.e(TAG, "init: No currentAccount available") + view?.finish() + } + } + + fun setEnabled(enabled: Boolean) { + mAccount!!.isEnabled = enabled + mAccountService.setAccountEnabled(mAccount!!.accountID, enabled) + } + + fun twoStatePreferenceChanged(configKey: ConfigKey, newValue: Any) { + if (configKey === ConfigKey.ACCOUNT_ENABLE) { + setEnabled(newValue as Boolean) + } else { + mAccount!!.setDetail(configKey, newValue.toString()) + updateAccount() + } + } + + fun passwordPreferenceChanged(configKey: ConfigKey, newValue: Any) { + if (mAccount!!.isSip) { + mAccount!!.credentials[0].setDetail(configKey, newValue.toString()) + } + updateAccount() + } + + fun userNameChanged(configKey: ConfigKey, newValue: Any) { + if (mAccount!!.isSip) { + mAccount!!.setDetail(configKey, newValue.toString()) + mAccount!!.credentials[0].setDetail(configKey, newValue.toString()) + } + updateAccount() + } + + fun preferenceChanged(configKey: ConfigKey, newValue: Any) { + mAccount!!.setDetail(configKey, newValue.toString()) + updateAccount() + } + + private fun updateAccount() { + mAccountService.setCredentials(mAccount!!.accountID, mAccount!!.credentialsHashMapList) + mAccountService.setAccountDetails(mAccount!!.accountID, mAccount!!.details) + } + + fun removeAccount() { + mAccountService.removeAccount(mAccount!!.accountID) + } + + companion object { + private val TAG = GeneralAccountPresenter::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/settings/GeneralAccountView.kt b/ring-android/libringclient/src/main/java/net/jami/settings/GeneralAccountView.kt new file mode 100644 index 0000000000000000000000000000000000000000..7e69d0919a87ad82472c84f94413c59d7819735c --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/settings/GeneralAccountView.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.settings + +import net.jami.model.Account +import net.jami.utils.Tuple + +interface GeneralAccountView { + fun addJamiPreferences(accountId: String) + fun addSipPreferences() + fun accountChanged(account: Account) + fun finish() + fun updateResolutions(maxResolution: Tuple<Int?, Int?>?, currentResolution: Int) +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/settings/MediaPreferencePresenter.kt b/ring-android/libringclient/src/main/java/net/jami/settings/MediaPreferencePresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..ea56fe7f865cb424db7b0de2bfc6c696e7450b0a --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/settings/MediaPreferencePresenter.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.settings + +import io.reactivex.rxjava3.core.Scheduler +import net.jami.model.Account +import net.jami.model.Codec +import net.jami.model.ConfigKey +import net.jami.mvp.RootPresenter +import net.jami.services.AccountService +import net.jami.services.DeviceRuntimeService +import net.jami.utils.Log +import java.util.* +import javax.inject.Inject +import javax.inject.Named + +class MediaPreferencePresenter @Inject constructor( + private var mAccountService: AccountService, + private var mDeviceRuntimeService: DeviceRuntimeService, + @Named("UiScheduler") + private var mUiScheduler: Scheduler +) : RootPresenter<MediaPreferenceView>() { + private var mAccount: Account? = null + + fun init(accountId: String) { + mAccount = mAccountService.getAccount(accountId) + mCompositeDisposable.clear() + mCompositeDisposable.add(mAccountService + .getObservableAccount(accountId) + .switchMapSingle { account: Account -> + mAccountService.getCodecList(accountId) + .observeOn(mUiScheduler) + .doOnSuccess { codecList: List<Codec> -> + val audioCodec = ArrayList<Codec>() + val videoCodec = ArrayList<Codec>() + for (codec in codecList) { + if (codec.type === Codec.Type.AUDIO) { + audioCodec.add(codec) + } else if (codec.type === Codec.Type.VIDEO) { + videoCodec.add(codec) + } + } + view?.accountChanged(account, audioCodec, videoCodec) + } + } + .subscribe({ }) { Log.e(TAG, "Error loading codec list") }) + } + + fun codecChanged(codecs: ArrayList<Long>) { + mAccountService.setActiveCodecList(mAccount!!.accountID, codecs) + } + + fun videoPreferenceChanged(key: ConfigKey, newValue: Any) { + mAccount!!.setDetail(key, newValue.toString()) + updateAccount() + } + + private fun updateAccount() { + mAccountService.setCredentials(mAccount!!.accountID, mAccount!!.credentialsHashMapList) + mAccountService.setAccountDetails(mAccount!!.accountID, mAccount!!.details) + } + + companion object { + val TAG = MediaPreferencePresenter::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferenceView.java b/ring-android/libringclient/src/main/java/net/jami/settings/MediaPreferenceView.java similarity index 97% rename from ring-android/app/src/main/java/cx/ring/fragments/MediaPreferenceView.java rename to ring-android/libringclient/src/main/java/net/jami/settings/MediaPreferenceView.java index 302054563ec506242595413c33d395c205159230..4f0da235143ae6b41ec68eaa1c8309c189223f9b 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/MediaPreferenceView.java +++ b/ring-android/libringclient/src/main/java/net/jami/settings/MediaPreferenceView.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ -package cx.ring.fragments; +package net.jami.settings; import java.util.ArrayList; diff --git a/ring-android/libringclient/src/main/java/net/jami/settings/SettingsPresenter.java b/ring-android/libringclient/src/main/java/net/jami/settings/SettingsPresenter.java deleted file mode 100644 index d46793bd78566db19eeef696af36125b3bd9c41b..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/settings/SettingsPresenter.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.settings; - -import javax.inject.Inject; -import javax.inject.Named; - -import net.jami.services.ConversationFacade; -import net.jami.model.Settings; -import net.jami.mvp.GenericView; -import net.jami.mvp.RootPresenter; -import net.jami.services.PreferencesService; -import net.jami.utils.Log; - -import io.reactivex.rxjava3.core.Scheduler; - -public class SettingsPresenter extends RootPresenter<GenericView<Settings>> { - - private final PreferencesService mPreferencesService; - private final Scheduler mUiScheduler; - private final ConversationFacade mConversationFacade; - - private final static String TAG = SettingsPresenter.class.getSimpleName(); - - - @Inject - public SettingsPresenter(PreferencesService preferencesService, ConversationFacade conversationFacade, @Named("UiScheduler") Scheduler uiScheduler) { - mPreferencesService = preferencesService; - mConversationFacade = conversationFacade; - mUiScheduler = uiScheduler; - } - - @Override - public void bindView(GenericView<Settings> view) { - super.bindView(view); - mCompositeDisposable.add(mPreferencesService.getSettingsSubject() - .subscribeOn(mUiScheduler) - .subscribe(settings -> getView().showViewModel(settings))); - } - - public void loadSettings() { - mPreferencesService.getSettings(); - } - - public void saveSettings(Settings settings) { - mPreferencesService.setSettings(settings); - } - - public void clearHistory() { - mCompositeDisposable.add(mConversationFacade.clearAllHistory().subscribe(() -> {}, e -> Log.e(TAG, "Error clearing app history", e))); - } - - public void setDarkMode(boolean isChecked) { - mPreferencesService.setDarkMode(isChecked); - } - - public boolean getDarkMode() { - return mPreferencesService.getDarkMode(); - } -} diff --git a/ring-android/libringclient/src/main/java/net/jami/settings/SettingsPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/settings/SettingsPresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..6c9b9e6bba9ccf448b646039cc24b6b1d7005d69 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/settings/SettingsPresenter.kt @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.settings + +import io.reactivex.rxjava3.core.Scheduler +import net.jami.model.Settings +import net.jami.mvp.GenericView +import net.jami.mvp.RootPresenter +import net.jami.services.ConversationFacade +import net.jami.services.PreferencesService +import net.jami.utils.Log +import javax.inject.Inject +import javax.inject.Named + +class SettingsPresenter @Inject constructor( + private val mPreferencesService: PreferencesService, + private val mConversationFacade: ConversationFacade, + @param:Named("UiScheduler") private val mUiScheduler: Scheduler +) : RootPresenter<GenericView<Settings>>() { + override fun bindView(view: GenericView<Settings>) { + super.bindView(view) + mCompositeDisposable.add(mPreferencesService.settingsSubject + .subscribeOn(mUiScheduler) + .subscribe { settings: Settings -> this.view?.showViewModel(settings) }) + } + + fun loadSettings() { + mPreferencesService.settings + } + + fun saveSettings(settings: Settings) { + mPreferencesService.settings = settings + } + + fun clearHistory() { + mCompositeDisposable.add( + mConversationFacade.clearAllHistory() + .subscribe({}) { e -> Log.e(TAG, "Error clearing app history", e) }) + } + + var darkMode: Boolean + get() = mPreferencesService.darkMode + set(isChecked) { + mPreferencesService.darkMode = isChecked + } + + companion object { + private val TAG = SettingsPresenter::class.simpleName!! + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/share/SharePresenter.java b/ring-android/libringclient/src/main/java/net/jami/share/SharePresenter.java deleted file mode 100644 index 91021983c22b0a1bedfef64dee27747f52db3f0f..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/share/SharePresenter.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.share; - -import net.jami.services.AccountService; - -import javax.inject.Inject; -import javax.inject.Named; - -import net.jami.mvp.GenericView; -import net.jami.mvp.RootPresenter; - -import io.reactivex.rxjava3.core.Scheduler; -import io.reactivex.rxjava3.schedulers.Schedulers; - -public class SharePresenter extends RootPresenter<GenericView<ShareViewModel>> { - private final AccountService mAccountService; - private final Scheduler mUiScheduler; - - @Inject - public SharePresenter(AccountService accountService, @Named("UiScheduler") Scheduler uiScheduler) { - mAccountService = accountService; - mUiScheduler = uiScheduler; - } - - @Override - public void bindView(GenericView<ShareViewModel> view) { - super.bindView(view); - mCompositeDisposable.add(mAccountService - .getCurrentAccountSubject() - .map(ShareViewModel::new) - .subscribeOn(Schedulers.computation()) - .observeOn(mUiScheduler) - .subscribe(this::loadContactInformation)); - } - - private void loadContactInformation(ShareViewModel model) { - GenericView<ShareViewModel> view = getView(); - if (view != null) { - view.showViewModel(model); - } - } -} diff --git a/ring-android/libringclient/src/main/java/net/jami/share/SharePresenter.kt b/ring-android/libringclient/src/main/java/net/jami/share/SharePresenter.kt new file mode 100644 index 0000000000000000000000000000000000000000..ffb6719a4581e601856aad75aebf78c7f498ceb1 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/share/SharePresenter.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.share + +import io.reactivex.rxjava3.core.Scheduler +import io.reactivex.rxjava3.schedulers.Schedulers +import net.jami.model.Account +import net.jami.mvp.GenericView +import net.jami.mvp.RootPresenter +import net.jami.services.AccountService +import javax.inject.Inject +import javax.inject.Named + +class SharePresenter @Inject constructor( + private val mAccountService: AccountService, + @param:Named("UiScheduler") private val mUiScheduler: Scheduler +) : RootPresenter<GenericView<ShareViewModel>>() { + override fun bindView(view: GenericView<ShareViewModel>) { + super.bindView(view) + mCompositeDisposable.add(mAccountService + .currentAccountSubject + .map { account: Account -> ShareViewModel(account) } + .subscribeOn(Schedulers.computation()) + .observeOn(mUiScheduler) + .subscribe { model: ShareViewModel -> loadContactInformation(model) }) + } + + private fun loadContactInformation(model: ShareViewModel) { + view?.showViewModel(model) + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/share/ShareViewModel.java b/ring-android/libringclient/src/main/java/net/jami/share/ShareViewModel.java deleted file mode 100644 index 2acf2f16ed64bd2366c1c79949de3b0bab83fd49..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/share/ShareViewModel.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.share; - -import net.jami.utils.QRCodeUtils; -import net.jami.model.Account; - -public class ShareViewModel { - - private final String shareUri; - private final String displayUri; - private final Account mAccount; - - public ShareViewModel(Account account) { - shareUri = account.getUri(); - displayUri = account.getDisplayUri(); - mAccount = account; - } - - public net.jami.utils.QRCodeUtils.QRCodeData getAccountQRCodeData(final int foregroundColor, final int backgroundColor) { - return QRCodeUtils.encodeStringAsQRCodeData(shareUri, foregroundColor, backgroundColor); - } - - public String getAccountShareUri() { - return shareUri; - } - - public String getAccountDisplayUri() { - return displayUri; - } - - public Account getAccount() { - return mAccount; - } - - -} diff --git a/ring-android/libringclient/src/main/java/net/jami/share/ShareViewModel.kt b/ring-android/libringclient/src/main/java/net/jami/share/ShareViewModel.kt new file mode 100644 index 0000000000000000000000000000000000000000..55d2a2021709096381bb948536c7bf9de49692a3 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/share/ShareViewModel.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.share + +import net.jami.model.Account +import net.jami.utils.QRCodeUtils.QRCodeData +import net.jami.utils.QRCodeUtils + +class ShareViewModel(val account: Account) { + private val accountShareUri: String? = account.uri + val accountDisplayUri: String? = account.displayUri + + fun getAccountQRCodeData(foregroundColor: Int, backgroundColor: Int): QRCodeData? { + return QRCodeUtils.encodeStringAsQRCodeData(accountShareUri, foregroundColor, backgroundColor) + } + +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListPresenter.kt b/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListPresenter.kt index 5787d744f3d7daf1444292bdcd979a3cfa28e5b8..cdec0ddd7c84c2ee0952c75ad6ecd043e2d6495d 100644 --- a/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListPresenter.kt +++ b/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListPresenter.kt @@ -77,7 +77,7 @@ class SmartListPresenter @Inject constructor( startConversation(viewModel.accountId, viewModel.uri) } - fun conversationLongClicked(smartListViewModel: SmartListViewModel?) { + fun conversationLongClicked(smartListViewModel: SmartListViewModel) { view!!.displayConversationDialog(smartListViewModel) } @@ -93,7 +93,7 @@ class SmartListPresenter @Inject constructor( } } - fun startConversation(uri: Uri?) { + fun startConversation(uri: Uri) { view!!.goToConversation(mAccount!!.accountID, uri) } @@ -143,16 +143,16 @@ class SmartListPresenter @Inject constructor( view!!.setLoading(true) mCompositeDisposable.add(conversations .switchMap { viewModels: List<Observable<SmartListViewModel>> -> - if (viewModels.isEmpty()) SmartListViewModel.EMPTY_RESULTS else Observable.combineLatest<SmartListViewModel, List<SmartListViewModel>>( - viewModels - ) { obs: Array<Any> -> //obs.map { it as SmartListViewModel } + if (viewModels.isEmpty()) + SmartListViewModel.EMPTY_RESULTS + else Observable.combineLatest(viewModels) { obs: Array<Any> -> //obs.map { it as SmartListViewModel } val vms: MutableList<SmartListViewModel> = ArrayList(obs.size) for (ob in obs) vms.add(ob as SmartListViewModel) vms }.throttleLatest(150, TimeUnit.MILLISECONDS, mUiScheduler) } .observeOn(mUiScheduler) - .subscribe({ viewModels: List<SmartListViewModel> -> + .subscribe({ viewModels: MutableList<SmartListViewModel> -> val view = view view!!.setLoading(false) if (viewModels.isEmpty()) { diff --git a/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListView.java b/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListView.java deleted file mode 100644 index a043e9a5d7845b489ad38ad47780921ed61d96f1..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListView.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.smartlist; - -import java.util.List; - -import net.jami.model.Uri; -import net.jami.mvp.BaseView; - -import io.reactivex.rxjava3.disposables.CompositeDisposable; - -public interface SmartListView extends BaseView { - - void displayChooseNumberDialog(CharSequence[] numbers); - - void displayNoConversationMessage(); - - void displayConversationDialog(SmartListViewModel smartListViewModel); - - void displayClearDialog(Uri callContact); - - void displayDeleteDialog(Uri callContact); - - void copyNumber(Uri uri); - - void setLoading(boolean display); - - void displayMenuItem(); - - void hideList(); - - void hideNoConversationMessage(); - - void updateList(List<SmartListViewModel> smartListViewModels, CompositeDisposable parentDisposable); - void update(SmartListViewModel model); - void update(int position); - - void goToConversation(String accountId, Uri contactId); - - void goToCallActivity(String accountId, Uri conversationUri, String contactId); - - void goToQRFragment(); - - void scrollToTop(); -} diff --git a/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListView.kt b/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListView.kt new file mode 100644 index 0000000000000000000000000000000000000000..7147e2f6fbf8cba420e1397dbf4bd3d99de709f0 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListView.kt @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Hadrien De Sousa <hadrien.desousa@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.smartlist + +import io.reactivex.rxjava3.disposables.CompositeDisposable +import net.jami.model.Uri + +interface SmartListView { + fun displayChooseNumberDialog(numbers: Array<CharSequence>) + fun displayNoConversationMessage() + fun displayConversationDialog(smartListViewModel: SmartListViewModel) + fun displayClearDialog(conversationUri: Uri) + fun displayDeleteDialog(conversationUri: Uri) + fun copyNumber(uri: Uri) + fun setLoading(loading: Boolean) + fun displayMenuItem() + fun hideList() + fun hideNoConversationMessage() + fun updateList(smartListViewModels: MutableList<SmartListViewModel>?, parentDisposable: CompositeDisposable) + fun update(model: SmartListViewModel) + fun update(position: Int) + fun goToConversation(accountId: String, conversationUri: Uri) + fun goToCallActivity(accountId: String, conversationUri: Uri, contactId: String) + fun goToQRFragment() + fun scrollToTop() +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListViewModel.kt b/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListViewModel.kt index 9e6ab56e8683c8ae52da7e3b70823ef4daef1515..da6b36570abad4da1f9707976369c878b83052d6 100644 --- a/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListViewModel.kt +++ b/ring-android/libringclient/src/main/java/net/jami/smartlist/SmartListViewModel.kt @@ -99,7 +99,7 @@ class SmartListViewModel { headerTitle = Title.None } - constructor(conversation: Conversation, presence: Boolean) : this(conversation, conversation.contacts, presence) {} + constructor(conversation: Conversation, presence: Boolean) : this(conversation, conversation.contacts, presence) private constructor(title: Title) { contactName = null @@ -166,7 +166,6 @@ class SmartListViewModel { val TITLE_CONVERSATIONS: Observable<SmartListViewModel> = Observable.just(SmartListViewModel(Title.Conversations)) val TITLE_PUBLIC_DIR: Observable<SmartListViewModel> = Observable.just(SmartListViewModel(Title.PublicDirectory)) val EMPTY_LIST: Single<List<Observable<SmartListViewModel>>> = Single.just(emptyList()) - @JvmStatic - val EMPTY_RESULTS: Observable<List<SmartListViewModel>> = Observable.just(emptyList()) + val EMPTY_RESULTS: Observable<MutableList<SmartListViewModel>> = Observable.just(ArrayList()) } } \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/Log.java b/ring-android/libringclient/src/main/java/net/jami/utils/Log.java deleted file mode 100644 index e075cb145c6c4c0f17175b11240d5079b68c9620..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/utils/Log.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.utils; - -import net.jami.services.LogService; - -public class Log { - private static LogService mLogService; - - public static void injectLogService(LogService service) { - mLogService = service; - } - - public static void d(String tag, String message) { - mLogService.d(tag, message); - } - - public static void e(String tag, String message) { - mLogService.e(tag, message); - } - - public static void i(String tag, String message) { - mLogService.i(tag, message); - } - - public static void w(String tag, String message) { - mLogService.w(tag, message); - } - - public static void d(String tag, String message, Throwable e) { - mLogService.d(tag, message, e); - } - - public static void e(String tag, String message, Throwable e) { - mLogService.e(tag, message, e); - } - - public static void i(String tag, String message, Throwable e) { - mLogService.i(tag, message, e); - } - - public static void w(String tag, String message, Throwable e) { - mLogService.w(tag, message, e); - } - -} diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/Log.kt b/ring-android/libringclient/src/main/java/net/jami/utils/Log.kt new file mode 100644 index 0000000000000000000000000000000000000000..31a979b979d320c54406e4720ba1b716ccbc9588 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/utils/Log.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.utils + +import net.jami.services.LogService + +object Log { + private lateinit var mLogService: LogService + + fun injectLogService(service: LogService) { + mLogService = service + } + + @JvmStatic + fun d(tag: String, message: String) { + mLogService.d(tag, message) + } + + fun e(tag: String, message: String) { + mLogService.e(tag, message) + } + + fun i(tag: String, message: String) { + mLogService.i(tag, message) + } + + @JvmStatic + fun w(tag: String, message: String) { + mLogService.w(tag, message) + } + + fun d(tag: String, message: String, e: Throwable) { + mLogService.d(tag, message, e) + } + + @JvmStatic + fun e(tag: String, message: String, e: Throwable) { + mLogService.e(tag, message, e) + } + + fun i(tag: String, message: String, e: Throwable) { + mLogService.i(tag, message, e) + } + + fun w(tag: String, message: String, e: Throwable) { + mLogService.w(tag, message, e) + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/NameLookupInputHandler.java b/ring-android/libringclient/src/main/java/net/jami/utils/NameLookupInputHandler.java deleted file mode 100644 index 3eeb57eda2232cc24ca4c6ad37b3d6a558cedb2a..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/utils/NameLookupInputHandler.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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 net.jami.utils; - -import net.jami.services.AccountService; - -import java.lang.ref.WeakReference; -import java.util.Timer; -import java.util.TimerTask; - -public class NameLookupInputHandler { - private static final int WAIT_DELAY = 350; - private final WeakReference<AccountService> mAccountService; - private final String mAccountId; - private final Timer timer = new Timer(true); - private NameTask lastTask = null; - - public NameLookupInputHandler(AccountService accountService, String accountId) { - mAccountService = new WeakReference<>(accountService); - mAccountId = accountId; - } - - public void enqueueNextLookup(String text) { - if (lastTask != null) { - lastTask.cancel(); - } - lastTask = new NameTask(text); - timer.schedule(lastTask, WAIT_DELAY); - } - - private class NameTask extends TimerTask { - private final String mTextToLookup; - - NameTask(String name) { - mTextToLookup = name; - } - - @Override - public void run() { - final AccountService accountService = mAccountService.get(); - if (accountService != null) { - accountService.lookupName(mAccountId, "", mTextToLookup); - } - } - } -} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/NameLookupInputHandler.kt b/ring-android/libringclient/src/main/java/net/jami/utils/NameLookupInputHandler.kt new file mode 100644 index 0000000000000000000000000000000000000000..f8db27ecdf0560faaa4a57dcbb5c99f7cac0b0a0 --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/utils/NameLookupInputHandler.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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 net.jami.utils + +import net.jami.services.AccountService +import java.lang.ref.WeakReference +import java.util.* + +class NameLookupInputHandler(accountService: AccountService, accountId: String) { + private val mAccountService: WeakReference<AccountService> = WeakReference(accountService) + private val mAccountId: String = accountId + private val timer = Timer(true) + private var lastTask: NameTask? = null + + fun enqueueNextLookup(text: String) { + lastTask?.cancel() + lastTask = NameTask(text) + timer.schedule(lastTask, WAIT_DELAY.toLong()) + } + + private inner class NameTask(private val mTextToLookup: String) : TimerTask() { + override fun run() { + mAccountService.get()?.lookupName(mAccountId, "", mTextToLookup) + } + } + + companion object { + private const val WAIT_DELAY = 350 + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/QRCodeUtils.java b/ring-android/libringclient/src/main/java/net/jami/utils/QRCodeUtils.java deleted file mode 100644 index 1a05f3198cd86caf8a68463e229cc3c60206203e..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/utils/QRCodeUtils.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -package net.jami.utils; - -import com.google.zxing.BarcodeFormat; -import com.google.zxing.EncodeHintType; -import com.google.zxing.WriterException; -import com.google.zxing.common.BitMatrix; -import com.google.zxing.qrcode.QRCodeWriter; - -import java.util.HashMap; - -public class QRCodeUtils { - - private final static String TAG = QRCodeUtils.class.getName(); - - private final static int QRCODE_IMAGE_SIZE = 256; - private final static int QRCODE_IMAGE_PADDING = 1; - - /** - * @param input uri to be displayed - * @return the resulting data - */ - public static QRCodeData encodeStringAsQRCodeData(String input, final int foregroundColor, final int backgroundColor) { - - if (input == null || input.isEmpty()) { - return null; - } - - QRCodeWriter qrWriter = new QRCodeWriter(); - BitMatrix qrImageMatrix; - try { - - HashMap<EncodeHintType, Integer> hints = new HashMap<>(); - hints.put(EncodeHintType.MARGIN, QRCODE_IMAGE_PADDING); - - qrImageMatrix = qrWriter.encode(input, BarcodeFormat.QR_CODE, QRCODE_IMAGE_SIZE, QRCODE_IMAGE_SIZE, hints); - } catch (WriterException e) { - Log.e(TAG, "Error while encoding QR", e); - return null; - } - - int qrImageWidth = qrImageMatrix.getWidth(); - int qrImageHeight = qrImageMatrix.getHeight(); - int[] pixels = new int[qrImageWidth * qrImageHeight]; - - for (int row = 0; row < qrImageHeight; row++) { - int offset = row * qrImageWidth; - for (int column = 0; column < qrImageWidth; column++) { - pixels[offset + column] = qrImageMatrix.get(column, row) ? foregroundColor : backgroundColor; - } - } - - return new QRCodeData(pixels, qrImageWidth, qrImageHeight); - } - - public static class QRCodeData { - private final int[] mData; - private final int mWidth; - private final int mHeight; - - public QRCodeData(int[] data, int width, int height) { - mData = data; - mWidth = width; - mHeight = height; - } - - public int[] getData() { - return mData; - } - - public int getWidth() { - return mWidth; - } - - public int getHeight() { - return mHeight; - } - } - -} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/QRCodeUtils.kt b/ring-android/libringclient/src/main/java/net/jami/utils/QRCodeUtils.kt new file mode 100644 index 0000000000000000000000000000000000000000..e08885ab350283dd30cfc89960471424360b423c --- /dev/null +++ b/ring-android/libringclient/src/main/java/net/jami/utils/QRCodeUtils.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.utils + +import com.google.zxing.BarcodeFormat +import com.google.zxing.EncodeHintType +import com.google.zxing.WriterException +import com.google.zxing.common.BitMatrix +import com.google.zxing.qrcode.QRCodeWriter +import net.jami.utils.Log.e +import java.util.* + +object QRCodeUtils { + private val TAG = QRCodeUtils::class.simpleName!! + private const val QRCODE_IMAGE_SIZE = 256 + private const val QRCODE_IMAGE_PADDING = 1 + + /** + * @param input uri to be displayed + * @return the resulting data + */ + fun encodeStringAsQRCodeData(input: String?, foregroundColor: Int, backgroundColor: Int): QRCodeData? { + if (input == null || input.isEmpty()) { + return null + } + val qrWriter = QRCodeWriter() + val qrImageMatrix: BitMatrix + try { + val hints = HashMap<EncodeHintType, Int?>() + hints[EncodeHintType.MARGIN] = QRCODE_IMAGE_PADDING + qrImageMatrix = qrWriter.encode(input, BarcodeFormat.QR_CODE, QRCODE_IMAGE_SIZE, QRCODE_IMAGE_SIZE, hints) + } catch (e: WriterException) { + e(TAG, "Error while encoding QR", e) + return null + } + val qrImageWidth = qrImageMatrix.getWidth() + val qrImageHeight = qrImageMatrix.getHeight() + val pixels = IntArray(qrImageWidth * qrImageHeight) + for (row in 0 until qrImageHeight) { + val offset = row * qrImageWidth + for (column in 0 until qrImageWidth) { + pixels[offset + column] = if (qrImageMatrix[column, row]) foregroundColor else backgroundColor + } + } + return QRCodeData(pixels, qrImageWidth, qrImageHeight) + } + + class QRCodeData(val data: IntArray, val width: Int, val height: Int) +} \ No newline at end of file diff --git a/ring-android/libringclient/src/main/java/net/jami/utils/VCardUtils.kt b/ring-android/libringclient/src/main/java/net/jami/utils/VCardUtils.kt index 4cd45da4d5b05a7d57b8d4d4d700e1e13abd6cd4..89c9d94d86f817009f9570c82ef66e89223a698f 100644 --- a/ring-android/libringclient/src/main/java/net/jami/utils/VCardUtils.kt +++ b/ring-android/libringclient/src/main/java/net/jami/utils/VCardUtils.kt @@ -40,7 +40,7 @@ import java.lang.Exception import java.util.HashMap object VCardUtils { - val TAG = VCardUtils::class.simpleName + val TAG = VCardUtils::class.simpleName!! const val MIME_PROFILE_VCARD = "x-ring/ring.profile.vcard" const val VCARD_KEY_MIME_TYPE = "mimeType" const val VCARD_KEY_PART = "part" @@ -48,7 +48,7 @@ object VCardUtils { const val LOCAL_USER_VCARD_NAME = "profile.vcf" private const val VCARD_MAX_SIZE = 1024L * 1024L * 8 - fun readData(vcard: VCard?): Tuple<String?, ByteArray?> { + fun readData(vcard: VCard?): Pair<String?, ByteArray?> { var contactName: String? = null var photo: ByteArray? = null if (vcard != null) { @@ -67,7 +67,7 @@ object VCardUtils { } } } - return Tuple(contactName, photo) + return Pair(contactName, photo) } fun writeData(uri: String?, displayName: String?, picture: ByteArray?): VCard { @@ -106,7 +106,6 @@ object VCardUtils { saveToDisk(vcard, filename, peerProfilePath(filesDir, accountId)) } - @JvmStatic fun saveLocalProfileToDisk(vcard: VCard, accountId: String, filesDir: File): Single<VCard> { return Single.fromCallable { saveToDisk(vcard, LOCAL_USER_VCARD_NAME, localProfilePath(filesDir, accountId)) @@ -152,7 +151,6 @@ object VCardUtils { } } - @JvmStatic fun loadLocalProfileFromDiskWithDefault(filesDir: File, accountId: String): Single<VCard> { return loadLocalProfileFromDisk(filesDir, accountId) .onErrorReturn { e: Throwable? -> setupDefaultProfile(filesDir, accountId) } @@ -219,12 +217,22 @@ object VCardUtils { return vcard } - fun peerProfileReceived(filesDir: File, accountId: String, peerId: String, vcard: File?): Single<VCard> { + fun accountProfileReceived(filesDir: File, accountId: String, vcard: File): Single<VCard> { + return Single.fromCallable { + val card = loadFromDisk(vcard)!! + saveLocalProfileToDisk(card, accountId, filesDir) + .subscribeOn(Schedulers.io()) + .subscribe({}) { e -> Log.e(TAG, "Error while saving vcard", e) } + card + }.subscribeOn(Schedulers.io()) + } + + fun peerProfileReceived(filesDir: File, accountId: String, peerId: String, vcard: File): Single<VCard> { return Single.fromCallable<VCard> { val filename = "$peerId.vcf" val peerProfilePath = peerProfilePath(filesDir, accountId) val file = File(peerProfilePath, filename) - moveFile(vcard!!, file) + moveFile(vcard, file) loadFromDisk(file) }.subscribeOn(Schedulers.io()) } diff --git a/ring-android/libringclient/src/main/java/net/jami/wizard/SIPCreationPresenter.java b/ring-android/libringclient/src/main/java/net/jami/wizard/SIPCreationPresenter.java deleted file mode 100644 index bbf6da8046387ac267e7ed0680ed1e1cbe0246a1..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/main/java/net/jami/wizard/SIPCreationPresenter.java +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Thibault Wittemberg <thibault.wittemberg@savoirfairelinux.com> - * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ -package net.jami.wizard; - -import net.jami.services.AccountService; -import net.jami.services.DeviceRuntimeService; - -import java.util.HashMap; -import java.util.Map; - -import javax.inject.Inject; -import javax.inject.Named; - -import net.jami.model.Account; -import net.jami.model.AccountConfig; -import net.jami.model.ConfigKey; -import net.jami.mvp.RootPresenter; -import net.jami.mvp.SIPCreationView; -import net.jami.utils.Log; -import net.jami.utils.VCardUtils; -import ezvcard.VCard; -import ezvcard.property.FormattedName; -import ezvcard.property.RawProperty; -import ezvcard.property.Uid; -import io.reactivex.rxjava3.annotations.NonNull; -import io.reactivex.rxjava3.core.Scheduler; -import io.reactivex.rxjava3.observers.DisposableObserver; - -public class SIPCreationPresenter extends RootPresenter<SIPCreationView> { - - private static final String TAG = SIPCreationPresenter.class.getSimpleName(); - - private net.jami.services.AccountService mAccountService; - - private net.jami.services.DeviceRuntimeService mDeviceService; - - private Account mAccount; - - @Inject - @Named("UiScheduler") - protected Scheduler mUiScheduler; - - - @Inject - public SIPCreationPresenter(AccountService mAccountService, DeviceRuntimeService mDeviceService) { - this.mAccountService = mAccountService; - this.mDeviceService = mDeviceService; - } - - @Override - public void bindView(SIPCreationView view) { - super.bindView(view); - } - - @Override - public void unbindView() { - super.unbindView(); - } - - /** - * Attempts to register the account specified by the form. If there are form errors (invalid or missing fields, etc.), the - * errors are presented and no actual creation attempt is made. - * - * @param hostname hostname account value - * @param username username account value - * @param password password account value - * @param bypassWarning Report eventual warning to the user - */ - public void startCreation(String hostname, String proxy, String username, String password, boolean bypassWarning) { - - getView().resetErrors(); - - // Store values at the time of the login attempt. - boolean warningIPAccount = false; - - if (hostname != null && hostname.isEmpty()) { - warningIPAccount = true; - } - - if (!warningIPAccount && (password == null || password.trim().isEmpty())) { - getView().showPasswordError(); - return; - } - - if (!warningIPAccount && (username == null || username.trim().isEmpty())) { - getView().showUsernameError(); - return; - } - - if (warningIPAccount && !bypassWarning) { - getView().showIP2IPWarning(); - } else { - HashMap<String, String> accountDetails = initAccountDetails(); - - if (accountDetails != null) { - accountDetails.put(net.jami.model.ConfigKey.ACCOUNT_ALIAS.key(), username); - if (hostname != null && !hostname.isEmpty()) { - accountDetails.put(ConfigKey.ACCOUNT_HOSTNAME.key(), hostname); - accountDetails.put(ConfigKey.ACCOUNT_ROUTESET.key(), proxy); - accountDetails.put(ConfigKey.ACCOUNT_USERNAME.key(), username); - accountDetails.put(ConfigKey.ACCOUNT_PASSWORD.key(), password); - } - } - registerAccount(accountDetails); - } - } - - public void removeAccount() { - if (mAccount != null) { - mAccountService.removeAccount(mAccount.getAccountID()); - mAccount = null; - } - } - - private void registerAccount(final HashMap<String, String> accountDetails) { - getView().showLoading(); - mCompositeDisposable.add( - mAccountService.addAccount(accountDetails) - .observeOn(mUiScheduler) - .subscribeWith(new DisposableObserver<Account>() { - @Override - public void onNext(@NonNull Account account) { - mAccount = account; - switch (account.getRegistrationState()) { - case AccountConfig.STATE_REGISTERED: - case net.jami.model.AccountConfig.STATE_SUCCESS: - case AccountConfig.STATE_READY: - saveProfile(account.getAccountID()); - getView().showRegistrationSuccess(); - dispose(); - break; - case AccountConfig.STATE_ERROR_NETWORK: - getView().showRegistrationNetworkError(); - dispose(); - break; - case AccountConfig.STATE_TRYING: - case AccountConfig.STATE_UNREGISTERED: - return; - default: - getView().showRegistrationError(); - dispose(); - break; - } - } - - @Override - public void onError(@NonNull Throwable e) { - getView().showRegistrationError(); - dispose(); - } - - @Override - public void onComplete() { - dispose(); - } - })); - } - - private HashMap<String, String> initAccountDetails() { - - try { - HashMap<String, String> accountDetails = mAccountService.getAccountTemplate(AccountConfig.ACCOUNT_TYPE_SIP).blockingGet(); - for (Map.Entry<String, String> e : accountDetails.entrySet()) { - Log.d(TAG, "Default account detail: " + e.getKey() + " -> " + e.getValue()); - } - - accountDetails.put(ConfigKey.VIDEO_ENABLED.key(), Boolean.toString(true)); - - //~ Sipinfo is forced for any sipaccount since overrtp is not supported yet. - //~ This will have to be removed when it will be supported. - accountDetails.put(net.jami.model.ConfigKey.ACCOUNT_DTMF_TYPE.key(), "sipinfo"); - return accountDetails; - } catch (Exception e) { - Log.e(TAG, "Error creating account", e); - return null; - } - } - - private void saveProfile(String accountID) { - VCard vcard = new VCard(); - String formattedName = mAccount.getUsername(); - if (formattedName.isEmpty()) { - formattedName = mAccount.getAlias(); - } - vcard.setFormattedName(new FormattedName(formattedName)); - String vcardUid = formattedName + accountID; - vcard.setUid(new Uid(vcardUid)); - vcard.removeProperties(RawProperty.class); - VCardUtils.saveLocalProfileToDisk(vcard, accountID, mDeviceService.provideFilesDir()).subscribe(); - mAccount.resetProfile(); - } -} \ No newline at end of file diff --git a/ring-android/libringclient/src/test/java/net/jami/model/ConversationTest.java b/ring-android/libringclient/src/test/java/net/jami/model/ConversationTest.java deleted file mode 100644 index d854e0adf963594bc1501a387798666166853820..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/test/java/net/jami/model/ConversationTest.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Pierre Duchemin <pierre.duchemin@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -package net.jami.model; - -import org.junit.Before; -import org.junit.Test; - -import java.util.Random; - -import static org.junit.Assert.assertEquals; - -/** - * Some tests to ensure Conversation integrity - */ -public class ConversationTest { - - private Conversation conversation; - - @Before - public void setUp() { - Contact contact = new Contact(Uri.fromString("jami://test")); - conversation = new Conversation("", contact); - } - - @Test - public void init_test() { - Contact contact = new Contact(Uri.fromString("jami://test")); - conversation = new Conversation("", contact); - - assertEquals(conversation.getContact(), contact); - } - - @Test - public void getConference() throws Exception { - } - - @Test - public void addConference() throws Exception { - - } - - @Test - public void removeConference() throws Exception { - } - - @Test - public void setContact() throws Exception { - } - - @Test - public void isVisible() throws Exception { - } - - @Test - public void setVisible() throws Exception { - } - - @Test - public void getContact() throws Exception { - } - - @Test - public void addHistoryCall() throws Exception { - int oldSize = conversation.getAggregateHistory().size(); - conversation.addCall(new Call("Coucou", "ring:test", "1", conversation, conversation.getContact(), Call.Direction.INCOMING)); - int newSize = conversation.getAggregateHistory().size(); - - assertEquals(0, oldSize); - assertEquals(oldSize, newSize - 1); - } - - @Test - public void addTextMessage() throws Exception { - int oldSize = conversation.getAggregateHistory().size(); - conversation.addTextMessage(new TextMessage( "Coucou", "ring:test", "1", conversation, "Toi")); - int newSize = conversation.getAggregateHistory().size(); - - assertEquals(0, oldSize); - assertEquals(oldSize, newSize - 1); - } - - @Test - public void updateTextMessage() throws Exception { - } - - @Test - public void getHistory() throws Exception { - } - - @Test - public void getAggregateHistory() throws Exception { - } - - @Test - public void getAccountsUsed() throws Exception { - } - - @Test - public void getLastAccountUsed() throws Exception { - } - - @Test - public void getCurrentCall() throws Exception { - } - - @Test - public void getCurrentCalls() throws Exception { - } - - @Test - public void getHistoryCalls() throws Exception { - } - - @Test - public void getUnreadTextMessages() throws Exception { - } - - @Test - public void getRawHistory() throws Exception { - } - - @Test - public void findConversationElement() throws Exception { - } - - @Test - public void addFileTransfer() throws Exception { - int oldSize = conversation.getAggregateHistory().size(); - conversation.addFileTransfer(new DataTransfer(1L, "Coucoou", "ring:sfvfv", "photo.jpg", true, 10L, 0L, 0L)); - int newSize = conversation.getAggregateHistory().size(); - - assertEquals(0, oldSize); - assertEquals(oldSize, newSize - 1); - } - - @Test - public void addFileTransfer1() throws Exception { - } - - @Test - public void addFileTransfers() throws Exception { - } - - @Test - public void updateFileTransfer() throws Exception { - } - - @Test - public void removeAll() throws Exception { - int random = new Random().nextInt(20); - - for (int i = 0; i < random; i++) { - conversation.addTextMessage(new TextMessage( "Coucou", "ring:test", "1", conversation, "Toi")); - } - int newSize = conversation.getAggregateHistory().size(); - - conversation.removeAll(); - int lastSize = conversation.getAggregateHistory().size(); - - assertEquals(random, newSize); - assertEquals(0, lastSize); - } - -} diff --git a/ring-android/libringclient/src/test/java/net/jami/model/ConversationTest.kt b/ring-android/libringclient/src/test/java/net/jami/model/ConversationTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..8dd563e6b465a408fa5eaa9da383e16f2d7c95ac --- /dev/null +++ b/ring-android/libringclient/src/test/java/net/jami/model/ConversationTest.kt @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Pierre Duchemin <pierre.duchemin@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.model + +import net.jami.model.Uri.Companion.fromString +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import java.util.* + +/** + * Some tests to ensure Conversation integrity + */ +class ConversationTest { + private var conversation: Conversation? = null + @Before + fun setUp() { + val contact = Contact(fromString("jami://test")) + conversation = Conversation("", contact) + } + + @Test + fun init_test() { + val contact = Contact(fromString("jami://test")) + conversation = Conversation("", contact) + Assert.assertEquals(conversation!!.contact, contact) + } + + @get:Throws(Exception::class) + @get:Test + val conference: Unit + get() {} + + @Test + @Throws(Exception::class) + fun addConference() { + } + + @Test + @Throws(Exception::class) + fun removeConference() { + } + + @Test + @Throws(Exception::class) + fun setContact() { + } + + @get:Throws(Exception::class) + @get:Test + val isVisible: Unit + get() {} + + @Test + @Throws(Exception::class) + fun setVisible() { + } + + @get:Throws(Exception::class) + @get:Test + val contact: Unit + get() {} + + @Test + @Throws(Exception::class) + fun addHistoryCall() { + val oldSize = conversation!!.aggregateHistory.size + conversation!!.addCall( + Call( + "Coucou", + "ring:test", + "1", + conversation, + conversation!!.contact, + Call.Direction.INCOMING + ) + ) + val newSize = conversation!!.aggregateHistory.size + Assert.assertEquals(0, oldSize.toLong()) + Assert.assertEquals(oldSize.toLong(), (newSize - 1).toLong()) + } + + @Test + @Throws(Exception::class) + fun addTextMessage() { + val oldSize = conversation!!.aggregateHistory.size + conversation!!.addTextMessage(TextMessage("Coucou", "ring:test", "1", conversation, "Toi")) + val newSize = conversation!!.aggregateHistory.size + Assert.assertEquals(0, oldSize.toLong()) + Assert.assertEquals(oldSize.toLong(), (newSize - 1).toLong()) + } + + @Test + @Throws(Exception::class) + fun updateTextMessage() { + } + + @get:Throws(Exception::class) + @get:Test + val history: Unit + get() {} + + @get:Throws(Exception::class) + @get:Test + val aggregateHistory: Unit + get() {} + + @get:Throws(Exception::class) + @get:Test + val accountsUsed: Unit + get() {} + + @get:Throws(Exception::class) + @get:Test + val lastAccountUsed: Unit + get() {} + + @get:Throws(Exception::class) + @get:Test + val currentCall: Unit + get() {} + + @get:Throws(Exception::class) + @get:Test + val currentCalls: Unit + get() {} + + @get:Throws(Exception::class) + @get:Test + val historyCalls: Unit + get() {} + + @get:Throws(Exception::class) + @get:Test + val unreadTextMessages: Unit + get() {} + + @get:Throws(Exception::class) + @get:Test + val rawHistory: Unit + get() {} + + @Test + @Throws(Exception::class) + fun findConversationElement() { + } + + @Test + @Throws(Exception::class) + fun addFileTransfer() { + val oldSize = conversation!!.aggregateHistory.size + conversation!!.addFileTransfer(DataTransfer("1", "Coucoou", "ring:sfvfv", "photo.jpg", true, 10L, 0L, 0L)) + val newSize = conversation!!.aggregateHistory.size + Assert.assertEquals(0, oldSize.toLong()) + Assert.assertEquals(oldSize.toLong(), (newSize - 1).toLong()) + } + + @Test + @Throws(Exception::class) + fun addFileTransfer1() { + } + + @Test + @Throws(Exception::class) + fun addFileTransfers() { + } + + @Test + @Throws(Exception::class) + fun updateFileTransfer() { + } + + @Test + @Throws(Exception::class) + fun removeAll() { + val random = Random().nextInt(20) + for (i in 0 until random) { + conversation!!.addTextMessage(TextMessage("Coucou", "ring:test", "1", conversation, "Toi")) + } + val newSize = conversation!!.aggregateHistory.size + conversation!!.removeAll() + val lastSize = conversation!!.aggregateHistory.size + Assert.assertEquals(random.toLong(), newSize.toLong()) + Assert.assertEquals(0, lastSize.toLong()) + } +} \ No newline at end of file diff --git a/ring-android/libringclient/src/test/java/net/jami/model/UriTest.java b/ring-android/libringclient/src/test/java/net/jami/model/UriTest.java deleted file mode 100644 index c962b68c91accd813af2e6b1300d544af6f76889..0000000000000000000000000000000000000000 --- a/ring-android/libringclient/src/test/java/net/jami/model/UriTest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2004-2021 Savoir-faire Linux Inc. - * - * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> - * Author: Pierre Duchemin <pierre.duchemin@savoirfairelinux.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, write to the Free Software - * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - */ - -package net.jami.model; - -import net.jami.utils.Tuple; - -import org.junit.Test; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -public class UriTest { - - @Test - public void testGoodRawString() { - String uri = "ring:1234567890123456789012345678901234567890"; - Uri test = Uri.fromString(uri); - assertTrue(test.getRawUriString().contentEquals(uri)); - } - - @Test - public void testBadIPAddress() { - assertFalse(Uri.isIpAddress("not an ip")); - } - - @Test - public void testGoodIPAddress() { - assertTrue(Uri.isIpAddress("127.0.0.1")); - assertTrue(Uri.isIpAddress("2001:db8:0:85a3:0:0:ac1f:8001")); - } - - @Test - public void testRingModel() { - String uri = "ring:1234567890123456789012345678901234567890"; - Tuple<Uri, String> test = Uri.fromStringWithName(uri); - - assertNull(test.second); - assertTrue(test.first.getScheme().contentEquals("ring:")); - assertTrue(test.first.getUri().contentEquals("ring:1234567890123456789012345678901234567890")); - } - - @Test - public void testSIPModel() { - String uri = "100@sipuri"; - Uri test = Uri.fromString(uri); - - assertTrue(test.getUsername().contentEquals("100")); - assertTrue(test.getHost().contentEquals("sipuri")); - } -} diff --git a/ring-android/libringclient/src/test/java/net/jami/model/UriTest.kt b/ring-android/libringclient/src/test/java/net/jami/model/UriTest.kt new file mode 100644 index 0000000000000000000000000000000000000000..4b330caab6a3593e1981d2719be5aabeb37c81b4 --- /dev/null +++ b/ring-android/libringclient/src/test/java/net/jami/model/UriTest.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2004-2021 Savoir-faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Author: Pierre Duchemin <pierre.duchemin@savoirfairelinux.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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +package net.jami.model + +import net.jami.model.Uri.Companion.fromString +import net.jami.model.Uri.Companion.fromStringWithName +import net.jami.model.Uri.Companion.isIpAddress +import org.junit.Assert +import org.junit.Test + +class UriTest { + @Test + fun testGoodRawString() { + val uri = "ring:1234567890123456789012345678901234567890" + val test = fromString(uri) + Assert.assertTrue(test.rawUriString.contentEquals(uri)) + } + + @Test + fun testBadIPAddress() { + Assert.assertFalse(isIpAddress("not an ip")) + } + + @Test + fun testGoodIPAddress() { + Assert.assertTrue(isIpAddress("127.0.0.1")) + Assert.assertTrue(isIpAddress("2001:db8:0:85a3:0:0:ac1f:8001")) + } + + @Test + fun testRingModel() { + val uri = "ring:1234567890123456789012345678901234567890" + val test = fromStringWithName(uri) + Assert.assertNull(test.second) + Assert.assertTrue(test.first.scheme.contentEquals("ring:")) + Assert.assertTrue(test.first.uri.contentEquals("ring:1234567890123456789012345678901234567890")) + } + + @Test + fun testSIPModel() { + val uri = "100@sipuri" + val test = fromString(uri) + Assert.assertTrue(test.username.contentEquals("100")) + Assert.assertTrue(test.host.contentEquals("sipuri")) + } +} \ No newline at end of file