Simplified mode setting

- Hide public room functionality (rationale: people chatting with strangers on the internet can be considered advanced users)
    - Clicking on + / Rooms goes directly to "Create new room" instead of the intermediate step which allows joining public rooms
    - The "New room" screen would hide the setting to make the room public and publish it in the room directory
    - Hide room address settings in the room settings
- Hide encryption settings (rationale: encryption is hard to explain to users without background knowledge)

Closes https://github.com/SpiritCroc/SchildiChat-android/issues/23

Change-Id: If1a7f16880ca2f4333270ec29cc1d27f70a9132d
This commit is contained in:
SpiritCroc 2020-11-16 18:10:18 +01:00
parent c3a20b5192
commit d850c7ac42
14 changed files with 357 additions and 48 deletions

View file

@ -97,6 +97,8 @@
</intent-filter>
</activity>
<activity android:name=".features.login.PromptSimplifiedModeActivity" />
<!-- Add tools:ignore="Instantiatable" for the error reported only by Buildkite :/ -->
<activity
android:name=".features.media.VectorAttachmentViewerActivity"

View file

@ -25,6 +25,7 @@ import im.vector.app.core.extensions.observeK
import im.vector.app.core.extensions.replaceChildFragment
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.features.grouplist.GroupListFragment
import im.vector.app.features.login.PromptSimplifiedModeActivity
import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.settings.VectorSettingsActivity
import im.vector.app.features.workers.signout.SignOutUiWorker
@ -82,4 +83,10 @@ class HomeDrawerFragment @Inject constructor(
navigator.openDebug(requireActivity())
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
PromptSimplifiedModeActivity.showIfRequired(requireContext(), vectorPreferences)
}
}

View file

@ -47,6 +47,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
import im.vector.app.features.home.room.list.widget.FabMenuView
import im.vector.app.features.notifications.NotificationDrawerManager
import im.vector.app.features.settings.VectorPreferences
import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.fragment_room_list.*
import org.matrix.android.sdk.api.failure.Failure
@ -65,8 +66,8 @@ class RoomListFragment @Inject constructor(
private val roomController: RoomSummaryController,
val roomListViewModelFactory: RoomListViewModel.Factory,
private val notificationDrawerManager: NotificationDrawerManager,
private val sharedViewPool: RecyclerView.RecycledViewPool
private val sharedViewPool: RecyclerView.RecycledViewPool,
private val vectorPreferences: VectorPreferences
) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, FabMenuView.Listener {
private var modelBuildListener: OnModelBuildFinishedListener? = null
@ -200,7 +201,12 @@ class RoomListFragment @Inject constructor(
}
override fun openRoomDirectory(initialFilter: String) {
navigator.openRoomDirectory(requireActivity(), initialFilter)
if (vectorPreferences.simplifiedMode()) {
// Simplified mode: don't browse room directories, just create a room
navigator.openCreateRoom(requireActivity())
} else {
navigator.openRoomDirectory(requireActivity(), initialFilter)
}
}
override fun createDirectChat() {

View file

@ -0,0 +1,55 @@
/*
* 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.app.features.login
import android.content.Context
import android.content.Intent
import androidx.appcompat.widget.Toolbar
import im.vector.app.R
import im.vector.app.core.extensions.addFragment
import im.vector.app.core.platform.ToolbarConfigurable
import im.vector.app.core.platform.VectorBaseActivity
import im.vector.app.features.pin.UnlockedActivity
import im.vector.app.features.settings.VectorPreferences
open class PromptSimplifiedModeActivity : VectorBaseActivity(), ToolbarConfigurable, UnlockedActivity {
final override fun getLayoutRes() = R.layout.activity_login
override fun initUiAndData() {
addFragment(R.id.loginFragmentContainer, PromptSimplifiedModeFragment::class.java)
}
companion object {
fun showIfRequired(context: Context, vectorPreferences: VectorPreferences) {
if (vectorPreferences.needsSimplifiedModePrompt()) {
context.startActivity(newIntent(context))
}
}
fun newIntent(context: Context): Intent {
return Intent(context, PromptSimplifiedModeActivity::class.java)
}
}
override fun configure(toolbar: Toolbar) {
configureToolbar(toolbar)
}
override fun onBackPressed() {
// Don't call super - we don't want to quit on back press, user should select a mode
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.app.features.login
import butterknife.OnClick
import im.vector.app.R
import im.vector.app.core.platform.VectorBaseFragment
import im.vector.app.features.settings.VectorPreferences
import javax.inject.Inject
class PromptSimplifiedModeFragment @Inject constructor() : VectorBaseFragment() {
override fun getLayoutResId() = R.layout.fragment_prompt_simplified_mode
@OnClick(R.id.promptSimplifiedModeOn)
fun simplifiedModeOn() {
VectorPreferences(requireContext()).setSimplifiedMode(true)
activity?.finish()
}
@OnClick(R.id.promptSimplifiedModeOff)
fun simplifiedModeOff() {
VectorPreferences(requireContext()).setSimplifiedMode(false)
activity?.finish()
}
}

View file

@ -31,10 +31,12 @@ import im.vector.app.features.form.formEditTextItem
import im.vector.app.features.form.formEditableAvatarItem
import im.vector.app.features.form.formSubmitButtonItem
import im.vector.app.features.form.formSwitchItem
import im.vector.app.features.settings.VectorPreferences
import javax.inject.Inject
class CreateRoomController @Inject constructor(private val stringProvider: StringProvider,
private val errorFormatter: ErrorFormatter
private val errorFormatter: ErrorFormatter,
private val vectorPreferences: VectorPreferences
) : TypedEpoxyController<CreateRoomViewState>() {
var listener: Listener? = null
@ -70,6 +72,8 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin
}
private fun buildForm(viewState: CreateRoomViewState, enableFormElement: Boolean) {
val enableNonSimplifiedMode = !vectorPreferences.simplifiedMode()
formEditableAvatarItem {
id("avatar")
enabled(enableFormElement)
@ -105,47 +109,50 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin
listener?.onTopicChange(text)
}
}
settingsSectionTitleItem {
id("settingsSection")
titleResId(R.string.create_room_settings_section)
}
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)
// Following settings are for advanced users only
if (enableNonSimplifiedMode) {
settingsSectionTitleItem {
id("settingsSection")
titleResId(R.string.create_room_settings_section)
}
}
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)
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?.setIsInRoomDirectory(value)
listener { value ->
listener?.setIsPublic(value)
}
}
}
formSwitchItem {
id("encryption")
enabled(enableFormElement)
title(stringProvider.getString(R.string.create_room_encryption_title))
summary(
if (viewState.hsAdminHasDisabledE2E) {
stringProvider.getString(R.string.settings_hs_admin_e2e_disabled)
} else {
stringProvider.getString(R.string.create_room_encryption_description)
}
)
switchChecked(viewState.isEncrypted)
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?.setIsEncrypted(value)
listener { value ->
listener?.setIsInRoomDirectory(value)
}
}
formSwitchItem {
id("encryption")
enabled(enableFormElement)
title(stringProvider.getString(R.string.create_room_encryption_title))
summary(
if (viewState.hsAdminHasDisabledE2E) {
stringProvider.getString(R.string.settings_hs_admin_e2e_disabled)
} else {
stringProvider.getString(R.string.create_room_encryption_description)
}
)
switchChecked(viewState.isEncrypted)
listener { value ->
listener?.setIsEncrypted(value)
}
}
}
formSubmitButtonItem {

View file

@ -26,6 +26,7 @@ import im.vector.app.features.form.formEditTextItem
import im.vector.app.features.form.formEditableAvatarItem
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.home.room.detail.timeline.format.RoomHistoryVisibilityFormatter
import im.vector.app.features.settings.VectorPreferences
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
@ -37,6 +38,7 @@ class RoomSettingsController @Inject constructor(
private val stringProvider: StringProvider,
private val avatarRenderer: AvatarRenderer,
private val roomHistoryVisibilityFormatter: RoomHistoryVisibilityFormatter,
private val vectorPreferences: VectorPreferences,
colorProvider: ColorProvider
) : TypedEpoxyController<RoomSettingsViewState>() {
@ -64,6 +66,7 @@ class RoomSettingsController @Inject constructor(
val historyVisibility = data.historyVisibilityEvent?.let { formatRoomHistoryVisibilityEvent(it) } ?: ""
val newHistoryVisibility = data.newHistoryVisibility?.let { roomHistoryVisibilityFormatter.format(it) }
val enableNonSimplifiedMode = !vectorPreferences.simplifiedMode()
formEditableAvatarItem {
id("avatar")
@ -110,14 +113,16 @@ class RoomSettingsController @Inject constructor(
}
}
formEditTextItem {
id("alias")
enabled(data.actionPermissions.canChangeCanonicalAlias)
value(data.newCanonicalAlias ?: roomSummary.canonicalAlias)
hint(stringProvider.getString(R.string.room_settings_addresses_add_new_address))
if (enableNonSimplifiedMode) {
formEditTextItem {
id("alias")
enabled(data.actionPermissions.canChangeCanonicalAlias)
value(data.newCanonicalAlias ?: roomSummary.canonicalAlias)
hint(stringProvider.getString(R.string.room_settings_addresses_add_new_address))
onTextChange { text ->
callback?.onAliasChanged(text)
onTextChange { text ->
callback?.onAliasChanged(text)
}
}
}

View file

@ -181,6 +181,7 @@ class VectorPreferences @Inject constructor(private val context: Context) {
private const val SETTINGS_SINGLE_OVERVIEW = "SETTINGS_SINGLE_OVERVIEW"
private const val SETTINGS_ROOM_UNREAD_KIND = "SETTINGS_ROOM_UNREAD_KIND"
private const val SETTINGS_UNIMPORTANT_COUNTER_BADGE = "SETTINGS_UNIMPORTANT_COUNTER_BADGE"
private const val SETTINGS_SIMPLIFIED_MODE = "SETTINGS_SIMPLIFIED_MODE"
private const val DID_ASK_TO_ENABLE_SESSION_PUSH = "DID_ASK_TO_ENABLE_SESSION_PUSH"
@ -865,6 +866,20 @@ class VectorPreferences @Inject constructor(private val context: Context) {
return defaultPrefs.getBoolean(SETTINGS_UNIMPORTANT_COUNTER_BADGE, true)
}
// SC addition
fun simplifiedMode(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_SIMPLIFIED_MODE, false)
}
fun needsSimplifiedModePrompt(): Boolean {
return !defaultPrefs.contains(SETTINGS_SIMPLIFIED_MODE)
}
fun setSimplifiedMode(simplified: Boolean) {
defaultPrefs
.edit()
.putBoolean(SETTINGS_SIMPLIFIED_MODE, simplified)
.apply()
}
/**
* The user enable protecting app access with pin code.
* Currently we use the pin code store to know if the pin is enabled, so this is not used

View file

@ -12,6 +12,7 @@
<ImageView
style="@style/LoginLogo"
android:id="@+id/loginLogo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"

View file

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<androidx.core.widget.NestedScrollView style="@style/LoginFormScrollView">
<androidx.constraintlayout.widget.ConstraintLayout style="@style/LoginFormContainer">
<ImageView
style="@style/LoginLogo"
android:id="@+id/loginLogo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/promptSimplifiedModeTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="36dp"
android:text="@string/prompt_simplified_mode_title"
android:textAppearance="@style/TextAppearance.Vector.Login.Title"
android:transitionName="loginTitleTransition"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/loginLogo" />
<TextView
android:id="@+id/promptSimplifiedModeText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:gravity="start"
android:text="@string/prompt_simplified_mode_text"
android:textAppearance="@style/TextAppearance.Vector.Login.Text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/promptSimplifiedModeTitle" />
<!-- Use a CheckableConstraintLayout to keep the pressed state when retrieving login flow -->
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/promptSimplifiedModeOn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:background="@drawable/bg_login_server_selector"
android:contentDescription="@string/prompt_simplified_mode_on"
android:paddingTop="@dimen/layout_vertical_margin"
android:paddingBottom="@dimen/layout_vertical_margin"
android:paddingStart="@dimen/layout_horizontal_margin"
android:paddingEnd="@dimen/layout_horizontal_margin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/promptSimplifiedModeText">
<TextView
android:id="@+id/promptSimplifiedModeOnTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start"
android:text="@string/prompt_simplified_mode_on"
android:textAppearance="@style/TextAppearance.Vector.Login.Text"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/promptSimplifiedModeOnText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/promptSimplifiedModeOnText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="start"
android:text="@string/prompt_simplified_mode_on_text"
android:textAppearance="@style/TextAppearance.Vector.Login.Text.Small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/promptSimplifiedModeOnTitle" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/promptSimplifiedModeOff"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:background="@drawable/bg_login_server_selector"
android:contentDescription="@string/prompt_simplified_mode_off"
android:paddingTop="@dimen/layout_vertical_margin"
android:paddingBottom="@dimen/layout_vertical_margin"
android:paddingStart="@dimen/layout_horizontal_margin"
android:paddingEnd="@dimen/layout_horizontal_margin"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/promptSimplifiedModeOn">
<TextView
android:id="@+id/promptSimplifiedModeOffTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start"
android:text="@string/prompt_simplified_mode_off"
android:textAppearance="@style/TextAppearance.Vector.Login.Text"
android:textColor="?riotx_text_primary"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@+id/promptSimplifiedModeOffText"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/promptSimplifiedModeOffText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:gravity="start"
android:text="@string/prompt_simplified_mode_off_text"
android:textAppearance="@style/TextAppearance.Vector.Login.Text.Small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/promptSimplifiedModeOffTitle" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>
</FrameLayout>

View file

@ -13,6 +13,16 @@
<string name="settings_room_unread_kind_content">Verstecke Teilnehmerveränderungen</string>
<string name="settings_room_unread_kind_original_content">Verstecke Teilnehmerveränderungen und Reaktionen</string>
<string name="settings_simplified_mode">Einfacher Modus</string>
<string name="settings_simplified_mode_summary">Verstecke erweiterte Funktionalität, einschließlich öffentlichen Raumverzeichnissen und Chat-Verschlüsselungseinstellungen</string>
<string name="prompt_simplified_mode_title">Benutzeroberfläche festlegen</string>
<string name="prompt_simplified_mode_text">Dies kann später in den Einstellungen geändert werden.</string>
<!-- simplified mode prompt: start and end with newline to make dialog look better -->
<string name="prompt_simplified_mode_on">Einfach</string>
<string name="prompt_simplified_mode_on_text">Ich möchte nur mit mir bekannten Leuten kommunizieren, und finde zusätzliche Optionen verwirrend.</string>
<string name="prompt_simplified_mode_off">Vollständig</string>
<string name="prompt_simplified_mode_off_text">Ich bevorzuge einen vollen Funktionsumfang, einschließlich den Zugriff auf öffentliche Raumverzeichnisse und erweiterte Raum- und Verschlüsselungseinstellungen.</string>
<string name="settings_light_theme">Helles Design</string>
<string name="settings_dark_theme">Dunkles Design</string>
<string name="settings_system_dark_theme_pre_ten">Folge dem Systemdesign</string>

View file

@ -32,4 +32,9 @@
<item>2</item>
</string-array>
<string-array name="prompt_simplified_mode_entries" translatable="false">
<item>@string/prompt_simplified_mode_on</item>
<item>@string/prompt_simplified_mode_off</item>
</string-array>
</resources>

View file

@ -13,6 +13,16 @@
<string name="settings_room_unread_kind_content">Hide member changes</string>
<string name="settings_room_unread_kind_original_content">Hide member changes and reactions</string>
<string name="settings_simplified_mode">Easy mode</string>
<string name="settings_simplified_mode_summary">Hide advanced functionality, including public room directories and chat encryption settings</string>
<string name="prompt_simplified_mode_title">Select user interface</string>
<string name="prompt_simplified_mode_text">You can change this later in the settings.</string>
<!-- simplified mode prompt: start and end with newline to make dialog look better -->
<string name="prompt_simplified_mode_on">Easy</string>
<string name="prompt_simplified_mode_on_text">I only plan to chat with people I know, and consider extra options confusing.</string>
<string name="prompt_simplified_mode_off">Complete</string>
<string name="prompt_simplified_mode_off_text">I\'m looking for full functionality, including discovery of public rooms and fine-grained room and encryption settings.</string>
<string name="settings_light_theme">Light theme</string>
<string name="settings_dark_theme">Dark theme</string>
<string name="settings_system_dark_theme_pre_ten">Follow system dark theme</string>

View file

@ -12,6 +12,13 @@
android:title="@string/settings_interface_language"
app:fragment="im.vector.app.features.settings.locale.LocalePickerFragment" />
<im.vector.app.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_SIMPLIFIED_MODE"
android:title="@string/settings_simplified_mode"
android:summary="@string/settings_simplified_mode_summary"
app:iconSpaceReserved="false" />
<im.vector.app.core.preference.VectorListPreference
android:defaultValue="sc_light"
android:entries="@array/theme_entries"