Skip to content

Commit 8cbba85

Browse files
criticalAYBrayanDSO
authored andcommitted
create MyAccount activity
update: MyAccount Unit Tests add activity to the activity list
1 parent c22a2d0 commit 8cbba85

5 files changed

Lines changed: 129 additions & 33 deletions

File tree

AnkiDroid/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@
384384
</intent-filter>
385385
</activity>
386386
<activity
387-
android:name="com.ichi2.anki.MyAccount"
387+
android:name="com.ichi2.anki.account.AccountActivity"
388388
android:label="@string/menu_my_account"
389389
android:exported="false"
390390
android:configChanges="keyboardHidden|orientation|screenSize"
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright (c) 2024 Ashish Yadav <mailtoashish693@gmail.com>
3+
*
4+
* This program is free software; you can redistribute it and/or modify it under
5+
* the terms of the GNU General Public License as published by the Free Software
6+
* Foundation; either version 3 of the License, or (at your option) any later
7+
* version.
8+
*
9+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
10+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11+
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12+
* details.
13+
*
14+
* You should have received a copy of the GNU General Public License along with
15+
* this program. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
package com.ichi2.anki.account
19+
20+
import android.content.Context
21+
import android.content.Intent
22+
import android.os.Build
23+
import androidx.activity.result.ActivityResultLauncher
24+
import androidx.core.os.bundleOf
25+
import com.ichi2.anki.SingleFragmentActivity
26+
import com.ichi2.anki.isLoggedIn
27+
import com.ichi2.utils.Permissions
28+
29+
class AccountActivity : SingleFragmentActivity() {
30+
companion object {
31+
/** Sees if we want to go back to the DeckPicker after login*/
32+
const val START_FROM_DECKPICKER = "START_FOR_RESULT"
33+
34+
/**
35+
* Displays a system prompt: "Allow AnkiDroid to send you notifications"
36+
*
37+
* [launcher] receives a callback result (`boolean`) unless:
38+
* * Permissions were already granted
39+
* * We are < API 33
40+
*
41+
* Permissions may permanently be denied, in which case [launcher] immediately
42+
* receives a failure result
43+
*/
44+
fun checkNotificationPermission(
45+
context: Context,
46+
launcher: ActivityResultLauncher<String>,
47+
) {
48+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
49+
50+
val permission = Permissions.postNotification ?: return
51+
52+
if (!Permissions.canPostNotifications(context)) {
53+
launcher.launch(permission)
54+
}
55+
}
56+
57+
/**
58+
* Returns an [Intent] to launch either [LoggedInFragment] or [LoginFragment]
59+
* based on the current login state.
60+
*
61+
* @param context The context used to create the intent.
62+
* @param forResult Indicates whether the calling component expects a result.
63+
* This is used to distinguish if the screen was launched from DeckPicker
64+
* or any other screen that needs a result back.
65+
*
66+
* @return An [Intent] to start the appropriate fragment.
67+
*/
68+
fun getIntent(
69+
context: Context,
70+
forResult: Boolean = false,
71+
): Intent =
72+
getIntent(
73+
context = context,
74+
fragmentClass = if (isLoggedIn()) LoggedInFragment::class else LoginFragment::class,
75+
arguments =
76+
bundleOf(
77+
START_FROM_DECKPICKER to forResult,
78+
),
79+
)
80+
}
81+
}

AnkiDroid/src/main/java/com/ichi2/anki/account/LoginFragment.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package com.ichi2.anki.account
1919

20+
import android.app.Activity.RESULT_OK
2021
import android.content.res.Configuration
2122
import android.os.Build
2223
import android.os.Bundle
@@ -30,13 +31,15 @@ import androidx.core.net.toUri
3031
import androidx.core.view.isVisible
3132
import androidx.core.widget.doOnTextChanged
3233
import androidx.fragment.app.Fragment
34+
import androidx.fragment.app.FragmentManager
3335
import androidx.fragment.app.viewModels
3436
import androidx.lifecycle.lifecycleScope
3537
import com.google.android.material.appbar.MaterialToolbar
3638
import com.google.android.material.textfield.TextInputEditText
3739
import com.google.android.material.textfield.TextInputLayout
3840
import com.ichi2.anki.CollectionManager.TR
3941
import com.ichi2.anki.R
42+
import com.ichi2.anki.account.AccountActivity.Companion.START_FROM_DECKPICKER
4043
import com.ichi2.anki.dialogs.help.HelpDialog
4144
import com.ichi2.anki.getEndpoint
4245
import com.ichi2.anki.snackbar.showSnackbar
@@ -208,7 +211,22 @@ class LoginFragment : Fragment(R.layout.my_account) {
208211
viewModel.loginState.collect { state ->
209212
when (state) {
210213
is LoginState.Success -> {
211-
// TODO: handle logged in state
214+
val activity = requireActivity()
215+
val isForResult = arguments?.getBoolean(START_FROM_DECKPICKER) ?: false
216+
217+
if (isForResult) {
218+
activity.setResult(RESULT_OK)
219+
activity.finish()
220+
} else {
221+
AccountActivity.checkNotificationPermission(requireContext(), notificationPermissionLauncher)
222+
223+
val fragmentManager = activity.supportFragmentManager
224+
fragmentManager
225+
.beginTransaction()
226+
.replace(R.id.fragment_container, LoggedInFragment())
227+
.commit()
228+
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
229+
}
212230
}
213231
is LoginState.Error -> {
214232
showSnackbar(text = state.exception.message.toString())

AnkiDroid/src/test/java/com/ichi2/anki/MyAccountTest.kt

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717

1818
package com.ichi2.anki
1919

20-
import android.content.Intent
2120
import android.widget.Button
2221
import androidx.test.ext.junit.runners.AndroidJUnit4
2322
import com.google.android.material.textfield.TextInputEditText
23+
import com.ichi2.anki.account.LoginFragment
2424
import com.ichi2.anki.settings.Prefs
25-
import com.ichi2.ui.TextInputEditField
25+
import com.ichi2.testutils.launchFragmentInContainer
2626
import junit.framework.TestCase.assertFalse
2727
import org.junit.Before
2828
import org.junit.Test
@@ -39,48 +39,43 @@ class MyAccountTest : RobolectricTest() {
3939

4040
@Test
4141
fun testLoginEmailPasswordProvided() {
42-
val myAccount =
43-
super.startActivityNormallyOpenCollectionWithIntent(
44-
MyAccount::class.java,
45-
Intent(),
46-
)
42+
val scenario = launchFragmentInContainer<LoginFragment>()
4743

48-
val testPassword = "randomStrongPassword"
49-
val testEmail = "random.email@example.com"
44+
scenario.onFragment { fragment ->
45+
val testPassword = "randomStrongPassword"
46+
val testEmail = "random.email@example.com"
5047

51-
myAccount.findViewById<TextInputEditText>(R.id.username).setText(testEmail)
52-
myAccount.findViewById<TextInputEditField>(R.id.password).setText(testPassword)
53-
val loginButton = myAccount.findViewById<Button>(R.id.login_button)
54-
assertTrue(loginButton.isEnabled)
48+
fragment.view?.findViewById<TextInputEditText>(R.id.username)?.setText(testEmail)
49+
fragment.view?.findViewById<TextInputEditText>(R.id.password)?.setText(testPassword)
50+
51+
val loginButton = fragment.view?.findViewById<Button>(R.id.login_button)
52+
assertTrue(loginButton?.isEnabled == true)
53+
}
5554
}
5655

5756
@Test
5857
fun testLoginFailsNoEmailProvided() {
59-
val myAccount =
60-
super.startActivityNormallyOpenCollectionWithIntent(
61-
MyAccount::class.java,
62-
Intent(),
63-
)
58+
val scenario = launchFragmentInContainer<LoginFragment>()
6459

65-
val testPassword = "randomStrongPassword"
60+
scenario.onFragment { fragment ->
61+
val testPassword = "randomStrongPassword"
6662

67-
myAccount.findViewById<TextInputEditField>(R.id.password).setText(testPassword)
68-
val loginButton = myAccount.findViewById<Button>(R.id.login_button)
69-
assertFalse(loginButton.isEnabled)
63+
fragment.view?.findViewById<TextInputEditText>(R.id.password)?.setText(testPassword)
64+
val loginButton = fragment.view?.findViewById<Button>(R.id.login_button)
65+
assertFalse(loginButton?.isEnabled == true)
66+
}
7067
}
7168

7269
@Test
7370
fun testLoginFailsNoPasswordProvided() {
74-
val myAccount =
75-
super.startActivityNormallyOpenCollectionWithIntent(
76-
MyAccount::class.java,
77-
Intent(),
78-
)
71+
val scenario = launchFragmentInContainer<LoginFragment>()
7972

80-
val testEmail = "random.email@example.com"
73+
scenario.onFragment { fragment ->
74+
val testEmail = "random.email@example.com"
8175

82-
myAccount.findViewById<TextInputEditText>(R.id.username).setText(testEmail)
83-
val loginButton = myAccount.findViewById<Button>(R.id.login_button)
84-
assertFalse(loginButton.isEnabled)
76+
fragment.view?.findViewById<TextInputEditText>(R.id.username)?.setText(testEmail)
77+
val loginButton = fragment.view?.findViewById<Button>(R.id.login_button)
78+
assertFalse(loginButton?.isEnabled == true)
79+
}
8580
}
8681
}

AnkiDroid/src/test/java/com/ichi2/testutils/ActivityList.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import com.ichi2.anki.Reviewer
4040
import com.ichi2.anki.SharedDecksActivity
4141
import com.ichi2.anki.SingleFragmentActivity
4242
import com.ichi2.anki.StudyOptionsActivity
43+
import com.ichi2.anki.account.AccountActivity
4344
import com.ichi2.anki.instantnoteeditor.InstantNoteEditorActivity
4445
import com.ichi2.anki.multimedia.MultimediaActivity
4546
import com.ichi2.anki.notetype.ManageNotetypes
@@ -97,6 +98,7 @@ object ActivityList {
9798
get(MultimediaActivity::class.java),
9899
get(DeckPickerWidgetConfig::class.java),
99100
get(CardAnalysisWidgetConfig::class.java),
101+
get(AccountActivity::class.java),
100102
)
101103

102104
private fun intentForCardTemplateBrowserAppearanceEditor(): Intent {

0 commit comments

Comments
 (0)