diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt index 8347d0af46..5f732d2b31 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/create/CreateRoomParams.kt @@ -100,7 +100,7 @@ class CreateRoomParams { * private_chat => join_rules is set to invite. history_visibility is set to shared. * trusted_private_chat => join_rules is set to invite. history_visibility is set to shared. All invitees are given the same power level as the * room creator. - * public_chat: => join_rules is set to public. history_visibility is set to shared. One of: ["private_chat", "public_chat", "trusted_private_chat"] + * public_chat: => join_rules is set to public. history_visibility is set to shared. */ var preset: CreateRoomPreset? = null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmQueryLatch.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmQueryLatch.kt index 8db44148d3..64afa3d494 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmQueryLatch.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmQueryLatch.kt @@ -18,12 +18,7 @@ package im.vector.matrix.android.internal.database import android.os.Handler import android.os.HandlerThread -import io.realm.Realm -import io.realm.RealmChangeListener -import io.realm.RealmConfiguration -import io.realm.RealmObject -import io.realm.RealmQuery -import io.realm.RealmResults +import io.realm.* import java.util.concurrent.CountDownLatch private const val THREAD_NAME = "REALM_QUERY_LATCH" @@ -39,6 +34,7 @@ class RealmQueryLatch(private val realmConfiguration: RealmConf val runnable = Runnable { val realm = Realm.getInstance(realmConfiguration) val result = realmQueryBuilder(realm).findAllAsync() + result.addChangeListener(object : RealmChangeListener> { override fun onChange(t: RealmResults) { if (t.isNotEmpty()) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt index b3aa137a03..7ac8e83efc 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/create/CreateRoomTask.kt @@ -16,23 +16,16 @@ package im.vector.matrix.android.internal.session.room.create -import android.os.Handler -import android.os.HandlerThread import arrow.core.Try import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomResponse +import im.vector.matrix.android.internal.database.RealmQueryLatch import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntityFields import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.task.Task -import io.realm.Realm -import io.realm.RealmChangeListener import io.realm.RealmConfiguration -import io.realm.RealmResults -import java.util.concurrent.CountDownLatch - -private const val THREAD_NAME = "CREATE_ROOM_" internal interface CreateRoomTask : Task @@ -47,41 +40,15 @@ internal class DefaultCreateRoomTask(private val roomAPI: RoomAPI, }.flatMap { createRoomResponse -> val roomId = createRoomResponse.roomId!! - val latch = CountDownLatch(1) - + // TODO Maybe do the same code for join room request ? // Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before) - val handlerThread = HandlerThread(THREAD_NAME + hashCode()) - handlerThread.start() - val handler = Handler(handlerThread.looper) - - handler.post { - val realm = Realm.getInstance(realmConfiguration) - - if (realm.where(RoomEntity::class.java) - .equalTo(RoomEntityFields.ROOM_ID, roomId) - .findAll() - .isEmpty()) { - val result = realm.where(RoomEntity::class.java) - .equalTo(RoomEntityFields.ROOM_ID, roomId) - .findAllAsync() - - result.addChangeListener(object : RealmChangeListener> { - override fun onChange(t: RealmResults) { - if (t.isNotEmpty()) { - result.removeChangeListener(this) - realm.close() - latch.countDown() - } - } - }) - } else { - realm.close() - latch.countDown() - } + val rql = RealmQueryLatch(realmConfiguration) { realm -> + realm.where(RoomEntity::class.java) + .equalTo(RoomEntityFields.ROOM_ID, roomId) } - latch.await() - handlerThread.quit() + rql.await() + return Try.just(roomId) } } diff --git a/vector/src/main/java/im/vector/riotredesign/core/mvrx/NavigationViewModel.kt b/vector/src/main/java/im/vector/riotredesign/core/mvrx/NavigationViewModel.kt new file mode 100644 index 0000000000..87b09c37ed --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/core/mvrx/NavigationViewModel.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotredesign.core.mvrx + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import im.vector.riotredesign.core.utils.LiveEvent + +abstract class NavigationViewModel : ViewModel() { + + private val _navigateTo = MutableLiveData>() + val navigateTo: LiveData> + get() = _navigateTo + + + fun goTo(navigation: NavigationClass) { + _navigateTo.postValue(LiveEvent(navigation)) + } +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/form/FormEditTextItem.kt b/vector/src/main/java/im/vector/riotredesign/features/form/FormEditTextItem.kt new file mode 100644 index 0000000000..e714f61e07 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/form/FormEditTextItem.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotredesign.features.form + +import android.text.Editable +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout +import im.vector.riotredesign.R +import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder +import im.vector.riotredesign.core.epoxy.VectorEpoxyModel +import im.vector.riotredesign.core.platform.SimpleTextWatcher + +@EpoxyModelClass(layout = R.layout.item_form_text_input) +abstract class FormEditTextItem : VectorEpoxyModel() { + + @EpoxyAttribute + var hint: String? = null + + @EpoxyAttribute + var value: String? = null + + @EpoxyAttribute + var enabled: Boolean = true + + @EpoxyAttribute + var onTextChange: ((String) -> Unit)? = null + + private val onTextChangeListener = object : SimpleTextWatcher() { + override fun afterTextChanged(s: Editable) { + onTextChange?.invoke(s.toString()) + } + } + + override fun bind(holder: Holder) { + holder.textInputLayout.isEnabled = enabled + holder.textInputLayout.hint = hint + + // Update only if text is different + if (holder.textInputEditText.text.toString() != value) { + holder.textInputEditText.setText(value) + } + holder.textInputEditText.isEnabled = enabled + + holder.textInputEditText.addTextChangedListener(onTextChangeListener) + } + + override fun shouldSaveViewState(): Boolean { + return false + } + + override fun unbind(holder: Holder) { + super.unbind(holder) + holder.textInputEditText.removeTextChangedListener(onTextChangeListener) + } + + class Holder : VectorEpoxyHolder() { + val textInputLayout by bind(R.id.formTextInputTextInputLayout) + val textInputEditText by bind(R.id.formTextInputTextInputEditText) + } +} + diff --git a/vector/src/main/java/im/vector/riotredesign/features/form/FormSwitchItem.kt b/vector/src/main/java/im/vector/riotredesign/features/form/FormSwitchItem.kt new file mode 100644 index 0000000000..6d8f627202 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/form/FormSwitchItem.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotredesign.features.form + +import android.widget.TextView +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import com.google.android.material.switchmaterial.SwitchMaterial +import im.vector.riotredesign.R +import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder +import im.vector.riotredesign.core.epoxy.VectorEpoxyModel +import im.vector.riotredesign.core.extensions.setTextOrHide + +@EpoxyModelClass(layout = R.layout.item_form_switch) +abstract class FormSwitchItem : VectorEpoxyModel() { + + @EpoxyAttribute + var listener: ((Boolean) -> Unit)? = null + + @EpoxyAttribute + var enabled: Boolean = true + + @EpoxyAttribute + var switchChecked: Boolean = false + + @EpoxyAttribute + var title: String? = null + + @EpoxyAttribute + var summary: String? = null + + override fun bind(holder: Holder) { + holder.titleView.text = title + holder.summaryView.setTextOrHide(summary) + + holder.switchView.isEnabled = enabled + + holder.switchView.isChecked = switchChecked + + holder.switchView.setOnCheckedChangeListener { _, isChecked -> + listener?.invoke(isChecked) + } + } + + override fun shouldSaveViewState(): Boolean { + return false + } + + override fun unbind(holder: Holder) { + super.unbind(holder) + + holder.switchView.setOnCheckedChangeListener(null) + } + + + class Holder : VectorEpoxyHolder() { + val titleView by bind(R.id.formSwitchTitle) + val summaryView by bind(R.id.formSwitchSummary) + val switchView by bind(R.id.formSwitchSwitch) + } + +} + diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt index 538666e5c1..379b3373f8 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeActivity.kt @@ -27,10 +27,12 @@ import androidx.core.view.GravityCompat import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.FragmentManager import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders import com.airbnb.mvrx.viewModel import im.vector.matrix.android.api.Matrix import im.vector.riotredesign.R import im.vector.riotredesign.core.extensions.hideKeyboard +import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.extensions.replaceFragment import im.vector.riotredesign.core.platform.OnBackPressed import im.vector.riotredesign.core.platform.ToolbarConfigurable @@ -46,7 +48,13 @@ import org.koin.android.scope.ext.android.getOrCreateScope class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { + // Supported navigation actions for this Activity + sealed class Navigation { + object OpenDrawer : Navigation() + } + private val homeActivityViewModel: HomeActivityViewModel by viewModel() + private lateinit var navigationViewModel: HomeNavigationViewModel private val homeNavigator by inject() private var progress: ProgressDialog? = null @@ -63,6 +71,9 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { super.onCreate(savedInstanceState) bindScope(getOrCreateScope(HomeModule.HOME_SCOPE)) homeNavigator.activity = this + + navigationViewModel = ViewModelProviders.of(this).get(HomeNavigationViewModel::class.java) + drawerLayout.addDrawerListener(drawerListener) if (isFirstCreation()) { val homeDrawerFragment = HomeDrawerFragment.newInstance() @@ -82,6 +93,12 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { progress?.dismiss() } }) + + navigationViewModel.navigateTo.observeEvent(this) { navigation -> + when (navigation) { + is Navigation.OpenDrawer -> drawerLayout.openDrawer(GravityCompat.START) + } + } } override fun onDestroy() { @@ -113,10 +130,6 @@ class HomeActivity : VectorBaseActivity(), ToolbarConfigurable { override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { - android.R.id.home -> { - drawerLayout.openDrawer(GravityCompat.START) - return true - } R.id.sliding_menu_sign_out -> { SignOutUiWorker(this).perform(Matrix.getInstance().currentSession!!) return true diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailFragment.kt index ef6543b186..cd47df2d38 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDetailFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.os.Parcelable import android.view.LayoutInflater import androidx.core.view.forEachIndexed +import androidx.lifecycle.ViewModelProviders import com.airbnb.mvrx.args import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState @@ -56,6 +57,7 @@ class HomeDetailFragment : VectorBaseFragment() { private lateinit var currentDisplayMode: RoomListFragment.DisplayMode private val viewModel: HomeDetailViewModel by fragmentViewModel() + private lateinit var navigationViewModel: HomeNavigationViewModel override fun getLayoutResId(): Int { return R.layout.fragment_home_detail @@ -65,6 +67,9 @@ class HomeDetailFragment : VectorBaseFragment() { super.onActivityCreated(savedInstanceState) currentDisplayMode = savedInstanceState?.getSerializable(CURRENT_DISPLAY_MODE) as? RoomListFragment.DisplayMode ?: RoomListFragment.DisplayMode.HOME + + navigationViewModel = ViewModelProviders.of(requireActivity()).get(HomeNavigationViewModel::class.java) + switchDisplayMode(currentDisplayMode) setupBottomNavigationView() setupToolbar() @@ -89,7 +94,7 @@ class HomeDetailFragment : VectorBaseFragment() { groupToolbarAvatarImageView ) groupToolbarAvatarImageView.setOnClickListener { - vectorBaseActivity.notImplemented("Group click in toolbar") + navigationViewModel.goTo(HomeActivity.Navigation.OpenDrawer) } } diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigationViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigationViewModel.kt new file mode 100644 index 0000000000..d922f3ae14 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigationViewModel.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotredesign.features.home + +import im.vector.riotredesign.core.mvrx.NavigationViewModel + +class HomeNavigationViewModel : NavigationViewModel() \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt index 44c1cfe366..e084b79832 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt @@ -16,10 +16,8 @@ package im.vector.riotredesign.features.navigation -import android.app.Activity import android.content.Context import android.content.Intent -import androidx.fragment.app.Fragment import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.riotredesign.features.debug.DebugMenuActivity import im.vector.riotredesign.features.home.room.detail.RoomDetailActivity diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt index 5deab028f2..f28c557658 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.view.MenuItem import android.view.View import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.epoxy.EpoxyVisibilityTracker import com.airbnb.mvrx.activityViewModel @@ -29,9 +30,7 @@ import com.jakewharton.rxbinding2.widget.RxTextView import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.riotredesign.R import im.vector.riotredesign.core.error.ErrorFormatter -import im.vector.riotredesign.core.extensions.addFragmentToBackstack import im.vector.riotredesign.core.platform.VectorBaseFragment -import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryPickerFragment import im.vector.riotredesign.features.themes.ThemeUtils import io.reactivex.rxkotlin.subscribeBy import kotlinx.android.synthetic.main.fragment_public_rooms.* @@ -49,6 +48,7 @@ import java.util.concurrent.TimeUnit class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback { private val viewModel: RoomDirectoryViewModel by activityViewModel() + private lateinit var navigationViewModel: RoomDirectoryNavigationViewModel private val publicRoomsController: PublicRoomsController by inject() private val errorFormatter: ErrorFormatter by inject() @@ -76,9 +76,7 @@ class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback .disposeOnDestroy() publicRoomsCreateNewRoom.setOnClickListener { - // TODO homeActivityViewModel.createRoom() - - vectorBaseActivity.notImplemented() + navigationViewModel.goTo(RoomDirectoryActivity.Navigation.CreateRoom) } viewModel.joinRoomErrorLiveData.observe(this, Observer { @@ -92,7 +90,7 @@ class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.menu_room_directory_change_protocol -> { - vectorBaseActivity.addFragmentToBackstack(RoomDirectoryPickerFragment(), R.id.simpleFragmentContainer) + navigationViewModel.goTo(RoomDirectoryActivity.Navigation.ChangeProtocol) true } else -> @@ -104,6 +102,8 @@ class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback super.onActivityCreated(savedInstanceState) bindScope(getOrCreateScope(RoomDirectoryModule.ROOM_DIRECTORY_SCOPE)) + navigationViewModel = ViewModelProviders.of(requireActivity()).get(RoomDirectoryNavigationViewModel::class.java) + setupRecyclerView() } diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryActivity.kt index c0ac91ee02..6f1e79e495 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryActivity.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryActivity.kt @@ -17,14 +17,29 @@ package im.vector.riotredesign.features.roomdirectory import android.os.Bundle +import androidx.lifecycle.ViewModelProviders import im.vector.riotredesign.R import im.vector.riotredesign.core.extensions.addFragment +import im.vector.riotredesign.core.extensions.addFragmentToBackstack +import im.vector.riotredesign.core.extensions.observeEvent import im.vector.riotredesign.core.platform.VectorBaseActivity +import im.vector.riotredesign.features.roomdirectory.createroom.CreateRoomFragment +import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryPickerFragment import org.koin.android.scope.ext.android.bindScope import org.koin.android.scope.ext.android.getOrCreateScope class RoomDirectoryActivity : VectorBaseActivity() { + // Supported navigation actions for this Activity + sealed class Navigation { + object Back : Navigation() + object CreateRoom : Navigation() + object Close : Navigation() + object ChangeProtocol : Navigation() + } + + + private lateinit var navigationViewModel: RoomDirectoryNavigationViewModel override fun getLayoutRes() = R.layout.activity_simple @@ -32,6 +47,17 @@ class RoomDirectoryActivity : VectorBaseActivity() { super.onCreate(savedInstanceState) bindScope(getOrCreateScope(RoomDirectoryModule.ROOM_DIRECTORY_SCOPE)) + + navigationViewModel = ViewModelProviders.of(this).get(RoomDirectoryNavigationViewModel::class.java) + + navigationViewModel.navigateTo.observeEvent(this) { navigation -> + when (navigation) { + is Navigation.Back -> onBackPressed() + is Navigation.CreateRoom -> addFragmentToBackstack(CreateRoomFragment(), R.id.simpleFragmentContainer) + is Navigation.ChangeProtocol -> addFragmentToBackstack(RoomDirectoryPickerFragment(), R.id.simpleFragmentContainer) + is Navigation.Close -> finish() + } + } } override fun initUiAndData() { diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryModule.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryModule.kt index 6dcdb93277..2ac228640c 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryModule.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryModule.kt @@ -17,11 +17,11 @@ package im.vector.riotredesign.features.roomdirectory import im.vector.matrix.android.api.session.Session +import im.vector.riotredesign.features.roomdirectory.createroom.CreateRoomController import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryListCreator import im.vector.riotredesign.features.roomdirectory.picker.RoomDirectoryPickerController import org.koin.dsl.module.module -// TODO Ganfra: When do we create a new module? class RoomDirectoryModule { companion object { @@ -41,5 +41,14 @@ class RoomDirectoryModule { scope(ROOM_DIRECTORY_SCOPE) { PublicRoomsController(get(), get()) } + + /* ========================================================================================== + * Create room + * ========================================================================================== */ + + scope(ROOM_DIRECTORY_SCOPE) { + CreateRoomController(get(), get()) + } + } } \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryNavigationViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryNavigationViewModel.kt new file mode 100644 index 0000000000..8183b7871f --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/RoomDirectoryNavigationViewModel.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotredesign.features.roomdirectory + +import im.vector.riotredesign.core.mvrx.NavigationViewModel + +class RoomDirectoryNavigationViewModel : NavigationViewModel() \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/createroom/CreateRoomController.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/createroom/CreateRoomController.kt new file mode 100644 index 0000000000..6d925e08eb --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/createroom/CreateRoomController.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotredesign.features.roomdirectory.createroom + +import com.airbnb.epoxy.TypedEpoxyController +import com.airbnb.mvrx.Fail +import com.airbnb.mvrx.Loading +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.Uninitialized +import im.vector.riotredesign.R +import im.vector.riotredesign.core.epoxy.errorWithRetryItem +import im.vector.riotredesign.core.epoxy.loadingItem +import im.vector.riotredesign.core.error.ErrorFormatter +import im.vector.riotredesign.core.resources.StringProvider +import im.vector.riotredesign.features.form.formEditTextItem +import im.vector.riotredesign.features.form.formSwitchItem + +class CreateRoomController(private val stringProvider: StringProvider, + private val errorFormatter: ErrorFormatter +) : TypedEpoxyController() { + + var listener: Listener? = null + + var index = 0 + + override fun buildModels(viewState: CreateRoomViewState) { + val asyncCreateRoom = viewState.asyncCreateRoomRequest + + when (asyncCreateRoom) { + is Success -> { + // Nothing to display, the screen will be closed + } + is Loading -> { + // display the form + buildForm(viewState, false) + loadingItem { + id("loading") + } + } + is Uninitialized -> { + // display the form + buildForm(viewState, true) + } + is Fail -> { + // display the form + buildForm(viewState, true) + errorWithRetryItem { + id("error") + text(errorFormatter.toHumanReadable(asyncCreateRoom.error)) + listener { listener?.retry() } + } + } + } + } + + private fun buildForm(viewState: CreateRoomViewState, enableFormElement: Boolean) { + formEditTextItem { + id("name") + enabled(enableFormElement) + value(viewState.roomName) + hint(stringProvider.getString(R.string.create_room_name_hint)) + + onTextChange { text -> + listener?.onNameChange(text) + } + } + formSwitchItem { + id("public") + enabled(enableFormElement) + title(stringProvider.getString(R.string.create_room_public_title)) + summary(stringProvider.getString(R.string.create_room_public_description)) + switchChecked(viewState.isPublic) + + listener { value -> + listener?.setIsPublic(value) + } + } + formSwitchItem { + id("directory") + enabled(enableFormElement) + title(stringProvider.getString(R.string.create_room_directory_title)) + summary(stringProvider.getString(R.string.create_room_directory_description)) + switchChecked(viewState.isInRoomDirectory) + + listener { value -> + listener?.setIsInRoomDirectory(value) + } + } + } + + interface Listener { + fun onNameChange(newName: String) + fun setIsPublic(isPublic: Boolean) + fun setIsInRoomDirectory(isInRoomDirectory: Boolean) + fun retry() + } + +} diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/createroom/CreateRoomFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/createroom/CreateRoomFragment.kt new file mode 100644 index 0000000000..96e7c60cb1 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/createroom/CreateRoomFragment.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotredesign.features.roomdirectory.createroom + +import android.os.Bundle +import android.view.MenuItem +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.LinearLayoutManager +import com.airbnb.mvrx.Success +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import im.vector.riotredesign.R +import im.vector.riotredesign.core.platform.VectorBaseFragment +import im.vector.riotredesign.features.roomdirectory.RoomDirectoryActivity +import im.vector.riotredesign.features.roomdirectory.RoomDirectoryModule +import im.vector.riotredesign.features.roomdirectory.RoomDirectoryNavigationViewModel +import kotlinx.android.synthetic.main.fragment_create_room.* +import org.koin.android.ext.android.inject +import org.koin.android.scope.ext.android.bindScope +import org.koin.android.scope.ext.android.getOrCreateScope +import timber.log.Timber + +class CreateRoomFragment : VectorBaseFragment(), CreateRoomController.Listener { + + private lateinit var navigationViewModel: RoomDirectoryNavigationViewModel + private val viewModel: CreateRoomViewModel by fragmentViewModel() + private val createRoomController: CreateRoomController by inject() + + override fun getLayoutResId() = R.layout.fragment_create_room + + override fun getMenuRes() = R.menu.vector_room_creation + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + bindScope(getOrCreateScope(RoomDirectoryModule.ROOM_DIRECTORY_SCOPE)) + + vectorBaseActivity.setSupportActionBar(createRoomToolbar) + + navigationViewModel = ViewModelProviders.of(requireActivity()).get(RoomDirectoryNavigationViewModel::class.java) + + setupRecyclerView() + + createRoomClose.setOnClickListener { + navigationViewModel.goTo(RoomDirectoryActivity.Navigation.Back) + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.action_create_room -> { + viewModel.doCreateRoom() + true + } + else -> + super.onOptionsItemSelected(item) + } + } + + private fun setupRecyclerView() { + val layoutManager = LinearLayoutManager(context) + + createRoomForm.layoutManager = layoutManager + createRoomController.listener = this + + createRoomForm.setController(createRoomController) + } + + override fun onNameChange(newName: String) { + viewModel.setName(newName) + } + + override fun setIsPublic(isPublic: Boolean) { + viewModel.setIsPublic(isPublic) + } + + override fun setIsInRoomDirectory(isInRoomDirectory: Boolean) { + viewModel.setIsInRoomDirectory(isInRoomDirectory) + } + + override fun retry() { + Timber.v("Retry") + viewModel.doCreateRoom() + } + + override fun invalidate() = withState(viewModel) { state -> + val async = state.asyncCreateRoomRequest + if (async is Success) { + // Navigate to freshly created room + navigator.openRoom(async(), requireActivity()) + + navigationViewModel.goTo(RoomDirectoryActivity.Navigation.Close) + } else { + // Populate list with Epoxy + createRoomController.setData(state) + } + } + + +} diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/createroom/CreateRoomViewModel.kt new file mode 100644 index 0000000000..3790381a5e --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -0,0 +1,81 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotredesign.features.roomdirectory.createroom + +import com.airbnb.mvrx.* +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.room.model.RoomDirectoryVisibility +import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams +import im.vector.matrix.android.api.session.room.model.create.CreateRoomPreset +import im.vector.riotredesign.core.platform.VectorViewModel +import org.koin.android.ext.android.get + +class CreateRoomViewModel(initialState: CreateRoomViewState, + private val session: Session) : VectorViewModel(initialState) { + + companion object : MvRxViewModelFactory { + + @JvmStatic + override fun create(viewModelContext: ViewModelContext, state: CreateRoomViewState): CreateRoomViewModel? { + val currentSession = viewModelContext.activity.get() + + return CreateRoomViewModel(state, currentSession) + } + } + + fun setName(newName: String) = setState { copy(roomName = newName) } + + fun setIsPublic(isPublic: Boolean) = setState { copy(isPublic = isPublic) } + + fun setIsInRoomDirectory(isInRoomDirectory: Boolean) = setState { copy(isInRoomDirectory = isInRoomDirectory) } + + fun doCreateRoom() = withState { state -> + if (state.asyncCreateRoomRequest is Loading || state.asyncCreateRoomRequest is Success) { + return@withState + } + + setState { + copy(asyncCreateRoomRequest = Loading()) + } + + val createRoomParams = CreateRoomParams().apply { + name = state.roomName.takeIf { it.isNotBlank() } + + // Directory visibility + visibility = if (state.isInRoomDirectory) RoomDirectoryVisibility.PUBLIC else RoomDirectoryVisibility.PRIVATE + + // Public room + preset = if (state.isPublic) CreateRoomPreset.PRESET_PUBLIC_CHAT else CreateRoomPreset.PRESET_PRIVATE_CHAT + } + + session.createRoom(createRoomParams, object : MatrixCallback { + override fun onSuccess(data: String) { + setState { + copy(asyncCreateRoomRequest = Success(data)) + } + } + + override fun onFailure(failure: Throwable) { + setState { + copy(asyncCreateRoomRequest = Fail(failure)) + } + } + }) + } + +} \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/createroom/CreateRoomViewState.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/createroom/CreateRoomViewState.kt new file mode 100644 index 0000000000..42d9d14473 --- /dev/null +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/createroom/CreateRoomViewState.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2019 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.riotredesign.features.roomdirectory.createroom + +import com.airbnb.mvrx.Async +import com.airbnb.mvrx.MvRxState +import com.airbnb.mvrx.Uninitialized + +data class CreateRoomViewState( + val roomName: String = "", + val isPublic: Boolean = false, + val isInRoomDirectory: Boolean = false, + val asyncCreateRoomRequest: Async = Uninitialized +) : MvRxState \ No newline at end of file diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt index 51369a91a1..4d1ca98b7e 100644 --- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt +++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/picker/RoomDirectoryPickerFragment.kt @@ -19,6 +19,7 @@ package im.vector.riotredesign.features.roomdirectory.picker import android.os.Bundle import android.view.MenuItem import android.view.View +import androidx.lifecycle.ViewModelProviders import androidx.recyclerview.widget.LinearLayoutManager import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel @@ -26,7 +27,9 @@ import com.airbnb.mvrx.withState import im.vector.matrix.android.api.session.room.model.thirdparty.RoomDirectoryData import im.vector.riotredesign.R import im.vector.riotredesign.core.platform.VectorBaseFragment +import im.vector.riotredesign.features.roomdirectory.RoomDirectoryActivity import im.vector.riotredesign.features.roomdirectory.RoomDirectoryModule +import im.vector.riotredesign.features.roomdirectory.RoomDirectoryNavigationViewModel import im.vector.riotredesign.features.roomdirectory.RoomDirectoryViewModel import kotlinx.android.synthetic.main.fragment_room_directory_picker.* import org.koin.android.ext.android.inject @@ -39,6 +42,7 @@ import timber.log.Timber class RoomDirectoryPickerFragment : VectorBaseFragment(), RoomDirectoryPickerController.Callback { private val viewModel: RoomDirectoryViewModel by activityViewModel() + private lateinit var navigationViewModel: RoomDirectoryNavigationViewModel private val pickerViewModel: RoomDirectoryPickerViewModel by fragmentViewModel() private val roomDirectoryPickerController: RoomDirectoryPickerController by inject() @@ -71,6 +75,8 @@ class RoomDirectoryPickerFragment : VectorBaseFragment(), RoomDirectoryPickerCon super.onActivityCreated(savedInstanceState) bindScope(getOrCreateScope(RoomDirectoryModule.ROOM_DIRECTORY_SCOPE)) + navigationViewModel = ViewModelProviders.of(requireActivity()).get(RoomDirectoryNavigationViewModel::class.java) + setupRecyclerView() } @@ -88,8 +94,7 @@ class RoomDirectoryPickerFragment : VectorBaseFragment(), RoomDirectoryPickerCon Timber.v("onRoomDirectoryClicked: $roomDirectoryData") viewModel.setRoomDirectoryData(roomDirectoryData) - // TODO Not the best way to manage Fragment Backstack... - vectorBaseActivity.onBackPressed() + navigationViewModel.goTo(RoomDirectoryActivity.Navigation.Back) } override fun retry() { diff --git a/vector/src/main/res/drawable/ic_x_18dp.xml b/vector/src/main/res/drawable/ic_x_18dp.xml new file mode 100644 index 0000000000..3172da8cc2 --- /dev/null +++ b/vector/src/main/res/drawable/ic_x_18dp.xml @@ -0,0 +1,14 @@ + + + diff --git a/vector/src/main/res/layout/fragment_create_room.xml b/vector/src/main/res/layout/fragment_create_room.xml new file mode 100644 index 0000000000..f5eeeb31a0 --- /dev/null +++ b/vector/src/main/res/layout/fragment_create_room.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/fragment_public_rooms.xml b/vector/src/main/res/layout/fragment_public_rooms.xml index 610e923383..de6fa43685 100644 --- a/vector/src/main/res/layout/fragment_public_rooms.xml +++ b/vector/src/main/res/layout/fragment_public_rooms.xml @@ -5,22 +5,33 @@ xmlns:tools="http://schemas.android.com/tools" android:id="@+id/publicRoomsCoordinator" android:layout_width="match_parent" - android:layout_height="match_parent" - android:background="?riotx_header_panel_background"> + android:layout_height="match_parent"> - + android:layout_height="match_parent" + android:background="?riotx_header_panel_background" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:listitem="@layout/item_public_room" /> + + + app:layout_constraintTop_toTopOf="parent" + app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways"> - - - + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_error_retry.xml b/vector/src/main/res/layout/item_error_retry.xml index dd3eb0a40e..f49beaea9e 100644 --- a/vector/src/main/res/layout/item_error_retry.xml +++ b/vector/src/main/res/layout/item_error_retry.xml @@ -5,6 +5,7 @@ android:id="@+id/progressBar" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="?riotx_background" android:padding="16dp"> + + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_form_text_input.xml b/vector/src/main/res/layout/item_form_text_input.xml new file mode 100644 index 0000000000..775489c5d9 --- /dev/null +++ b/vector/src/main/res/layout/item_form_text_input.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_no_result.xml b/vector/src/main/res/layout/item_no_result.xml index a64eda59c5..833a952cc3 100644 --- a/vector/src/main/res/layout/item_no_result.xml +++ b/vector/src/main/res/layout/item_no_result.xml @@ -3,6 +3,7 @@ android:id="@+id/itemNoResultText" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="?riotx_background" android:gravity="center" android:padding="16dp" android:text="@string/no_result_placeholder" /> diff --git a/vector/src/main/res/layout/item_public_room.xml b/vector/src/main/res/layout/item_public_room.xml index 6970e100e7..5c28baad5d 100644 --- a/vector/src/main/res/layout/item_public_room.xml +++ b/vector/src/main/res/layout/item_public_room.xml @@ -9,6 +9,7 @@ android:background="?riotx_background" android:clickable="true" android:focusable="true" + android:foreground="?attr/selectableItemBackground" android:minHeight="97dp"> + android:focusable="true" + android:foreground="?attr/selectableItemBackground"> diff --git a/vector/src/main/res/layout/item_room_directory.xml b/vector/src/main/res/layout/item_room_directory.xml index 6babf515a0..64679f7b37 100644 --- a/vector/src/main/res/layout/item_room_directory.xml +++ b/vector/src/main/res/layout/item_room_directory.xml @@ -6,6 +6,7 @@ android:id="@+id/itemRoomDirectoryLayout" android:layout_width="match_parent" android:layout_height="wrap_content" + android:background="?riotx_background" android:clickable="true" android:focusable="true" android:foreground="?attr/selectableItemBackground"> diff --git a/vector/src/main/res/menu/vector_room_creation.xml b/vector/src/main/res/menu/vector_room_creation.xml index 5b02163055..42a21da9ab 100755 --- a/vector/src/main/res/menu/vector_room_creation.xml +++ b/vector/src/main/res/menu/vector_room_creation.xml @@ -2,13 +2,11 @@ + tools:context=".features.roomdirectory.RoomDirectoryActivity"> diff --git a/vector/src/main/res/values-v21/theme_status.xml b/vector/src/main/res/values-v21/theme_status.xml index dfa41ebd09..3deb65a9d6 100644 --- a/vector/src/main/res/values-v21/theme_status.xml +++ b/vector/src/main/res/values-v21/theme_status.xml @@ -1,9 +1,14 @@ - + + + + +