Skip to content

Commit 788ebc5

Browse files
committed
Customized Settings as per MC with test cases.
Added delete account menu under settings. NMC-3041 NMC-4681 -- enable logs for debug builds NMC-4850 -- use appcompat text appearance instead of material for separating headings NMC-4888 -- add option to save logs
1 parent 5289f60 commit 788ebc5

File tree

26 files changed

+1513
-224
lines changed

26 files changed

+1513
-224
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* Nextcloud - Android Client
3+
*
4+
* SPDX-FileCopyrightText: 2025 TSI-mc <surinder.kumar@t-systems.com>
5+
* SPDX-License-Identifier: AGPL-3.0-or-later
6+
*/
7+
8+
package com.nmc.android
9+
10+
import android.content.Context
11+
import android.content.res.Configuration
12+
import androidx.test.core.app.ApplicationProvider
13+
import androidx.test.ext.junit.runners.AndroidJUnit4
14+
import com.owncloud.android.R
15+
import junit.framework.TestCase.assertEquals
16+
import org.junit.Test
17+
import org.junit.runner.RunWith
18+
import java.util.Locale
19+
20+
/**
21+
* Test class to verify the strings customized in this branch PR for NMC
22+
*/
23+
@RunWith(AndroidJUnit4::class)
24+
class SettingsResourceTest {
25+
26+
private val baseContext = ApplicationProvider.getApplicationContext<Context>()
27+
28+
private val localizedStringMap = mapOf(
29+
R.string.prefs_mnemonic_summary to ExpectedLocalizedString(
30+
translations = mapOf(
31+
Locale.ENGLISH to "Displays your 12 word key (passhprase)",
32+
Locale.GERMAN to "12-Wort-Schlüssel anzeigen (Passphrase)"
33+
)
34+
),
35+
R.string.prefs_keys_exist_summary to ExpectedLocalizedString(
36+
translations = mapOf(
37+
Locale.ENGLISH to "End-to-end encryption was already set up on another client. Please enter your mnemonic to allow this client to sync and decrypt the files.",
38+
Locale.GERMAN to "Die Ende-zu-Ende Verschlüsselung wurde bereits auf einem anderen Gerät eingerichtet. Bitte geben Sie Ihre Passphrase ein, damit die Dateien synchronisiert und entschlüsselt werden."
39+
)
40+
),
41+
R.string.actionbar_contacts to ExpectedLocalizedString(
42+
translations = mapOf(
43+
Locale.ENGLISH to "Back up contacts",
44+
Locale.GERMAN to "Kontakte sichern"
45+
)
46+
),
47+
R.string.actionbar_calendar_contacts_restore to ExpectedLocalizedString(
48+
translations = mapOf(
49+
Locale.ENGLISH to "Restore contacts and calendar",
50+
Locale.GERMAN to "Kontakte & Kalender wiederherstellen"
51+
)
52+
),
53+
R.string.prefs_category_account_info to ExpectedLocalizedString(
54+
translations = mapOf(
55+
Locale.ENGLISH to "Account Information",
56+
Locale.GERMAN to "Kontoinformationen"
57+
)
58+
),
59+
R.string.prefs_category_info to ExpectedLocalizedString(
60+
translations = mapOf(
61+
Locale.ENGLISH to "Info",
62+
Locale.GERMAN to "Info"
63+
)
64+
),
65+
R.string.prefs_category_data_privacy to ExpectedLocalizedString(
66+
translations = mapOf(
67+
Locale.ENGLISH to "Data Privacy",
68+
Locale.GERMAN to "Datenschutz"
69+
)
70+
),
71+
R.string.privacy_settings to ExpectedLocalizedString(
72+
translations = mapOf(
73+
Locale.ENGLISH to "Privacy Settings",
74+
Locale.GERMAN to "Datenschutz-Einstellungen"
75+
)
76+
),
77+
R.string.privacy_policy to ExpectedLocalizedString(
78+
translations = mapOf(
79+
Locale.ENGLISH to "Privacy Policy",
80+
Locale.GERMAN to "Datenschutzbestimmungen"
81+
)
82+
),
83+
R.string.prefs_delete_account to ExpectedLocalizedString(
84+
translations = mapOf(
85+
Locale.ENGLISH to "Delete account permanently",
86+
Locale.GERMAN to "Konto endgültig löschen"
87+
)
88+
),
89+
R.string.prefs_open_source to ExpectedLocalizedString(
90+
translations = mapOf(
91+
Locale.ENGLISH to "Used OpenSource Software",
92+
Locale.GERMAN to "Verwendete OpenSource Software"
93+
)
94+
),
95+
R.string.prefs_category_service to ExpectedLocalizedString(
96+
translations = mapOf(
97+
Locale.ENGLISH to "Service",
98+
Locale.GERMAN to "Bedienung"
99+
)
100+
),
101+
R.string.logs_menu_save to ExpectedLocalizedString(
102+
translations = mapOf(
103+
Locale.ENGLISH to "Save logs",
104+
Locale.GERMAN to "Protokolle speichern"
105+
)
106+
),
107+
R.string.logs_export_success to ExpectedLocalizedString(
108+
translations = mapOf(
109+
Locale.ENGLISH to "Logs saved successfully",
110+
Locale.GERMAN to "Protokolle erfolgreich gespeichert"
111+
)
112+
),
113+
R.string.logs_export_failed to ExpectedLocalizedString(
114+
translations = mapOf(
115+
Locale.ENGLISH to "Failed to save logs",
116+
Locale.GERMAN to "Fehler beim Speichern der Protokolle"
117+
)
118+
),
119+
R.string.url_delete_account to ExpectedLocalizedString(
120+
translations = mapOf(
121+
Locale.ENGLISH to "https://www.telekom.de/hilfe/vertrag-rechnung/login-daten-passwoerter/telekom-login-loeschen",
122+
)
123+
),
124+
R.string.url_imprint_nmc to ExpectedLocalizedString(
125+
translations = mapOf(
126+
Locale.ENGLISH to "https://www.telekom.de/impressum",
127+
)
128+
),
129+
)
130+
131+
@Test
132+
fun verifyLocalizedStrings() {
133+
localizedStringMap.forEach { (stringRes, expected) ->
134+
expected.translations.forEach { (locale, expectedText) ->
135+
136+
val config = Configuration(baseContext.resources.configuration)
137+
config.setLocale(locale)
138+
139+
val localizedContext = baseContext.createConfigurationContext(config)
140+
val actualText = localizedContext.getString(stringRes)
141+
142+
assertEquals(
143+
"Mismatch for ${baseContext.resources.getResourceEntryName(stringRes)} in $locale",
144+
expectedText,
145+
actualText
146+
)
147+
}
148+
}
149+
}
150+
151+
data class ExpectedLocalizedString(val translations: Map<Locale, String>)
152+
}
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
package com.nmc.android.ui
2+
3+
import android.preference.ListPreference
4+
import android.preference.Preference
5+
import androidx.test.espresso.Espresso.onData
6+
import androidx.test.espresso.assertion.ViewAssertions.matches
7+
import androidx.test.espresso.matcher.PreferenceMatchers
8+
import androidx.test.espresso.matcher.PreferenceMatchers.withKey
9+
import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed
10+
import androidx.test.ext.junit.rules.ActivityScenarioRule
11+
import com.owncloud.android.AbstractIT
12+
import com.owncloud.android.R
13+
import com.owncloud.android.ui.AppVersionPreference
14+
import com.owncloud.android.ui.PreferenceCustomCategory
15+
import com.owncloud.android.ui.ThemeableSwitchPreference
16+
import com.owncloud.android.ui.activity.SettingsActivity
17+
import org.hamcrest.Matchers.allOf
18+
import org.hamcrest.Matchers.instanceOf
19+
import org.hamcrest.Matchers.`is`
20+
import org.junit.Assert.assertEquals
21+
import org.junit.Rule
22+
import org.junit.Test
23+
24+
class SettingsPreferenceIT : AbstractIT() {
25+
26+
@get:Rule
27+
val activityRule = ActivityScenarioRule(SettingsActivity::class.java)
28+
29+
@Test
30+
fun verifyPreferenceSectionCustomClass() {
31+
activityRule.scenario.onActivity {
32+
val preferenceAccountInfo = it.findPreference("account_info")
33+
val preferenceGeneral = it.findPreference("general")
34+
val preferenceDetails = it.findPreference("details")
35+
val preferenceMore = it.findPreference("more")
36+
val preferenceDataProtection = it.findPreference("data_protection")
37+
val preferenceInfo = it.findPreference("info")
38+
39+
val preferenceCategoryList = listOf(
40+
preferenceAccountInfo,
41+
preferenceGeneral,
42+
preferenceDetails,
43+
preferenceMore,
44+
preferenceDataProtection,
45+
preferenceInfo
46+
)
47+
48+
for (preference in preferenceCategoryList) {
49+
assertEquals(PreferenceCustomCategory::class.java, preference.javaClass)
50+
}
51+
}
52+
}
53+
54+
@Test
55+
fun verifySwitchPreferenceCustomClass() {
56+
activityRule.scenario.onActivity {
57+
val preferenceShowHiddenFiles = it.findPreference("show_hidden_files")
58+
assertEquals(ThemeableSwitchPreference::class.java, preferenceShowHiddenFiles.javaClass)
59+
}
60+
}
61+
62+
@Test
63+
fun verifyAppVersionPreferenceCustomClass() {
64+
activityRule.scenario.onActivity {
65+
val preferenceAboutApp = it.findPreference("about_app")
66+
assertEquals(AppVersionPreference::class.java, preferenceAboutApp.javaClass)
67+
}
68+
}
69+
70+
@Test
71+
fun verifyPreferenceChildCustomLayout() {
72+
activityRule.scenario.onActivity {
73+
val userName = it.findPreference("user_name")
74+
val storagePath = it.findPreference("storage_path")
75+
val lock = it.findPreference("lock")
76+
val showHiddenFiles = it.findPreference("show_hidden_files")
77+
val syncedFolders = it.findPreference("syncedFolders")
78+
val backup = it.findPreference("backup")
79+
val mnemonic = it.findPreference("mnemonic")
80+
val privacySettings = it.findPreference("privacy_settings")
81+
val privacyPolicy = it.findPreference("privacy_policy")
82+
val sourceCode = it.findPreference("sourcecode")
83+
val help = it.findPreference("help")
84+
val imprint = it.findPreference("imprint")
85+
86+
val preferenceList = listOf(
87+
userName,
88+
storagePath,
89+
lock,
90+
showHiddenFiles,
91+
syncedFolders,
92+
backup,
93+
mnemonic,
94+
privacySettings,
95+
privacyPolicy,
96+
sourceCode,
97+
help,
98+
imprint
99+
)
100+
101+
for (preference in preferenceList) {
102+
assertEquals(R.layout.custom_preference_layout, preference.layoutResource)
103+
}
104+
105+
val aboutApp = it.findPreference("about_app")
106+
assertEquals(R.layout.custom_app_preference_layout, aboutApp.layoutResource)
107+
108+
}
109+
}
110+
111+
@Test
112+
fun verifyPreferencesTitleText() {
113+
onData(allOf(`is`(instanceOf(PreferenceCustomCategory::class.java)), withKey("account_info"),
114+
PreferenceMatchers.withTitleText("Account Information")))
115+
.check(matches(isCompletelyDisplayed()))
116+
117+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("user_name"),
118+
PreferenceMatchers.withTitleText("test")))
119+
.check(matches(isCompletelyDisplayed()))
120+
121+
onData(allOf(`is`(instanceOf(PreferenceCustomCategory::class.java)), withKey("general"),
122+
PreferenceMatchers.withTitleText("General")))
123+
.check(matches(isCompletelyDisplayed()))
124+
125+
onData(allOf(`is`(instanceOf(ListPreference::class.java)), withKey("storage_path"),
126+
PreferenceMatchers.withTitleText("Data storage folder")))
127+
.check(matches(isCompletelyDisplayed()))
128+
129+
onData(allOf(`is`(instanceOf(PreferenceCustomCategory::class.java)), withKey("details"),
130+
PreferenceMatchers.withTitleText("Details")))
131+
.check(matches(isCompletelyDisplayed()))
132+
133+
onData(allOf(`is`(instanceOf(ListPreference::class.java)), withKey("lock"),
134+
PreferenceMatchers.withTitleText("App passcode")))
135+
.check(matches(isCompletelyDisplayed()))
136+
137+
onData(allOf(`is`(instanceOf(ThemeableSwitchPreference::class.java)), withKey("show_hidden_files"),
138+
PreferenceMatchers.withTitleText("Show hidden files")))
139+
.check(matches(isCompletelyDisplayed()))
140+
141+
onData(allOf(`is`(instanceOf(PreferenceCustomCategory::class.java)), withKey("more"),
142+
PreferenceMatchers.withTitleText("More")))
143+
.check(matches(isCompletelyDisplayed()))
144+
145+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("syncedFolders"),
146+
PreferenceMatchers.withTitleText("Auto upload")))
147+
.check(matches(isCompletelyDisplayed()))
148+
149+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("backup"),
150+
PreferenceMatchers.withTitleText("Back up contacts")))
151+
.check(matches(isCompletelyDisplayed()))
152+
153+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("mnemonic"),
154+
PreferenceMatchers.withTitleText("E2E mnemonic")))
155+
.check(matches(isCompletelyDisplayed()))
156+
157+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("logger"),
158+
PreferenceMatchers.withTitleText("Logs")))
159+
.check(matches(isCompletelyDisplayed()))
160+
161+
onData(allOf(`is`(instanceOf(PreferenceCustomCategory::class.java)), withKey("data_protection"),
162+
PreferenceMatchers.withTitleText("Data Privacy")))
163+
.check(matches(isCompletelyDisplayed()))
164+
165+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("privacy_settings"),
166+
PreferenceMatchers.withTitleText("Privacy Settings")))
167+
.check(matches(isCompletelyDisplayed()))
168+
169+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("privacy_policy"),
170+
PreferenceMatchers.withTitleText("Privacy Policy")))
171+
.check(matches(isCompletelyDisplayed()))
172+
173+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("sourcecode"),
174+
PreferenceMatchers.withTitleText("Used OpenSource Software")))
175+
.check(matches(isCompletelyDisplayed()))
176+
177+
onData(allOf(`is`(instanceOf(PreferenceCustomCategory::class.java)), withKey("service"),
178+
PreferenceMatchers.withTitleText("Service")))
179+
.check(matches(isCompletelyDisplayed()))
180+
181+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("help"),
182+
PreferenceMatchers.withTitleText("Help")))
183+
.check(matches(isCompletelyDisplayed()))
184+
185+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("imprint"),
186+
PreferenceMatchers.withTitleText("Imprint")))
187+
.check(matches(isCompletelyDisplayed()))
188+
189+
onData(allOf(`is`(instanceOf(PreferenceCustomCategory::class.java)), withKey("info"),
190+
PreferenceMatchers.withTitleText("Info")))
191+
.check(matches(isCompletelyDisplayed()))
192+
}
193+
194+
@Test
195+
fun verifyPreferencesSummaryText() {
196+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("lock"),
197+
PreferenceMatchers.withSummaryText("None")))
198+
.check(matches(isCompletelyDisplayed()))
199+
200+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("syncedFolders"),
201+
PreferenceMatchers.withSummaryText("Manage folders for auto upload")))
202+
.check(matches(isCompletelyDisplayed()))
203+
204+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("backup"),
205+
PreferenceMatchers.withSummaryText("Daily backup of your calendar & contacts")))
206+
.check(matches(isCompletelyDisplayed()))
207+
208+
onData(allOf(`is`(instanceOf(Preference::class.java)), withKey("mnemonic"),
209+
PreferenceMatchers.withSummaryText("To show mnemonic please enable device credentials.")))
210+
.check(matches(isCompletelyDisplayed()))
211+
}
212+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?><!--
2+
~ Nextcloud - Android Client
3+
~
4+
~ SPDX-FileCopyrightText: 2025 Your Name <your@email.com>
5+
~ SPDX-License-Identifier: AGPL-3.0-or-later
6+
-->
7+
8+
<resources>
9+
<!-- enable logs in debug builds -->
10+
<bool name="logger_enabled">true</bool>
11+
</resources>

app/src/main/java/com/nextcloud/client/logger/ui/LogsActivity.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ class LogsActivity : ToolbarActivity() {
8585
android.R.id.home -> finish()
8686
R.id.action_delete_logs -> vm.deleteAll()
8787
R.id.action_send_logs -> vm.send()
88+
R.id.action_save_logs -> vm.save()
8889
R.id.action_refresh_logs -> vm.load()
8990
else -> retval = super.onOptionsItemSelected(item)
9091
}

0 commit comments

Comments
 (0)