Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: pre-login mobile app exploration #86

Merged
merged 10 commits into from
Dec 4, 2023
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Modern vision of the mobile application for the Open EdX platform from Raccoon G

3. Choose ``openedx-app-android``.

4. Configure the [config.yaml](config.yaml) with URLs and OAuth credentials for your Open edX instance.
4. Configure the [config.yaml](default_config/dev/config.yaml) with URLs and OAuth credentials for your Open edX instance.

5. Select the build variant ``develop``, ``stage``, or ``prod``.

Expand Down
26 changes: 16 additions & 10 deletions app/src/main/java/org/openedx/app/AppActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
import androidx.window.layout.WindowMetricsCalculator
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.openedx.app.databinding.ActivityAppBinding
import org.openedx.auth.presentation.logistration.LogistrationFragment
import org.openedx.auth.presentation.signin.SignInFragment
import org.openedx.core.data.storage.CorePreferences
import org.openedx.core.extension.requestApplyInsetsWhenAttached
Expand Down Expand Up @@ -110,30 +112,34 @@ class AppActivity : AppCompatActivity(), InsetHolder, WindowSizeHolder {
if (savedInstanceState == null) {
when {
corePreferencesManager.user == null -> {
supportFragmentManager.beginTransaction()
.add(R.id.container, SignInFragment())
.commit()
if (viewModel.isLogistrationEnabled) {
addFragment(LogistrationFragment())
} else {
addFragment(SignInFragment())
}
}

whatsNewManager.shouldShowWhatsNew() -> {
supportFragmentManager.beginTransaction()
.add(R.id.container, WhatsNewFragment())
.commit()
addFragment(WhatsNewFragment())
}

corePreferencesManager.user != null -> {
supportFragmentManager.beginTransaction()
.add(R.id.container, MainFragment())
.commit()
addFragment(MainFragment())
}
}
}

viewModel.logoutUser.observe(this) {
profileRouter.restartApp(supportFragmentManager)
profileRouter.restartApp(supportFragmentManager, viewModel.isLogistrationEnabled)
}
}

private fun addFragment(fragment: Fragment) {
supportFragmentManager.beginTransaction()
.add(R.id.container, fragment)
.commit()
}

private fun computeWindowSizeClasses() {
val metrics = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this)
Expand Down
44 changes: 35 additions & 9 deletions app/src/main/java/org/openedx/app/AppRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentTransaction
import org.openedx.auth.presentation.AuthRouter
import org.openedx.auth.presentation.logistration.LogistrationFragment
import org.openedx.auth.presentation.restore.RestorePasswordFragment
import org.openedx.auth.presentation.signin.SignInFragment
import org.openedx.auth.presentation.signup.SignUpFragment
Expand All @@ -23,6 +24,7 @@ import org.openedx.course.presentation.unit.container.CourseUnitContainerFragmen
import org.openedx.course.presentation.unit.video.VideoFullScreenFragment
import org.openedx.course.presentation.unit.video.YoutubeVideoFullScreenFragment
import org.openedx.dashboard.presentation.DashboardRouter
import org.openedx.discovery.presentation.DiscoveryFragment
import org.openedx.discovery.presentation.DiscoveryRouter
import org.openedx.discovery.presentation.search.CourseSearchFragment
import org.openedx.discussion.domain.model.DiscussionComment
Expand Down Expand Up @@ -56,6 +58,10 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
.commit()
}

override fun navigateToSignIn(fm: FragmentManager) {
replaceFragmentWithBackStack(fm, SignInFragment())
}

override fun navigateToSignUp(fm: FragmentManager) {
replaceFragmentWithBackStack(fm, SignUpFragment())
}
Expand All @@ -64,6 +70,10 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
replaceFragmentWithBackStack(fm, RestorePasswordFragment())
}

override fun navigateToDiscoverCourses(fm: FragmentManager, querySearch: String) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need querySearch after directly navigate to the course search screen.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed in DC, we will proceed with current flow

replaceFragmentWithBackStack(fm, DiscoveryFragment.newInstance(querySearch))
}

override fun navigateToWhatsNew(fm: FragmentManager) {
fm.popBackStack()
fm.beginTransaction()
Expand All @@ -77,8 +87,8 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
replaceFragmentWithBackStack(fm, CourseDetailsFragment.newInstance(courseId))
}

override fun navigateToCourseSearch(fm: FragmentManager) {
replaceFragmentWithBackStack(fm, CourseSearchFragment())
override fun navigateToCourseSearch(fm: FragmentManager, querySearch: String) {
replaceFragmentWithBackStack(fm, CourseSearchFragment.newInstance(querySearch))
}

override fun navigateToUpgradeRequired(fm: FragmentManager) {
Expand All @@ -105,7 +115,10 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
coursewareAccess: CoursewareAccess,
auditAccessExpires: Date?
) {
replaceFragment(fm, NoAccessCourseContainerFragment.newInstance(title,coursewareAccess, auditAccessExpires))
replaceFragment(
fm,
NoAccessCourseContainerFragment.newInstance(title, coursewareAccess, auditAccessExpires)
)
}
//endregion

Expand Down Expand Up @@ -174,7 +187,13 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
) {
replaceFragmentWithBackStack(
fm,
YoutubeVideoFullScreenFragment.newInstance(videoUrl, videoTime, blockId, courseId, isPlaying)
YoutubeVideoFullScreenFragment.newInstance(
videoUrl,
videoTime,
blockId,
courseId,
isPlaying
)
)
}

Expand Down Expand Up @@ -270,14 +289,17 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
replaceFragmentWithBackStack(fm, DeleteProfileFragment())
}

override fun restartApp(fm: FragmentManager) {
override fun restartApp(fm: FragmentManager, isLogistrationEnabled: Boolean) {
fm.apply {
for (fragment in fragments) {
beginTransaction().remove(fragment).commit()
}
popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
beginTransaction().replace(R.id.container, SignInFragment())
.commit()
if (isLogistrationEnabled) {
replaceFragment(fm, LogistrationFragment())
} else {
replaceFragment(fm, SignInFragment())
}
}
}
//endregion
Expand All @@ -289,7 +311,11 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
.commit()
}

private fun replaceFragment(fm: FragmentManager, fragment: Fragment, transaction: Int = FragmentTransaction.TRANSIT_NONE) {
private fun replaceFragment(
fm: FragmentManager,
fragment: Fragment,
transaction: Int = FragmentTransaction.TRANSIT_NONE
) {
fm.beginTransaction()
.setTransition(transaction)
.replace(R.id.container, fragment, fragment.javaClass.simpleName)
Expand All @@ -303,4 +329,4 @@ class AppRouter : AuthRouter, DiscoveryRouter, DashboardRouter, CourseRouter, Di
.replace(R.id.container, ProfileFragment())
.commit()
}
}
}
4 changes: 4 additions & 0 deletions app/src/main/java/org/openedx/app/AppViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import org.openedx.app.system.notifier.AppNotifier
import org.openedx.app.system.notifier.LogoutEvent
import org.openedx.core.BaseViewModel
import org.openedx.core.SingleEventLiveData
import org.openedx.core.config.Config
import org.openedx.core.data.storage.CorePreferences

class AppViewModel(
private val config: Config,
private val notifier: AppNotifier,
private val room: RoomDatabase,
private val preferencesManager: CorePreferences,
Expand All @@ -25,6 +27,8 @@ class AppViewModel(
val logoutUser: LiveData<Unit>
get() = _logoutUser

val isLogistrationEnabled get() = config.isPreLoginExperienceEnabled()

private var logoutHandledAt: Long = 0

override fun onCreate(owner: LifecycleOwner) {
Expand Down
150 changes: 133 additions & 17 deletions app/src/main/java/org/openedx/app/di/ScreenModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,57 +54,173 @@ import org.openedx.whatsnew.presentation.whatsnew.WhatsNewViewModel

val screenModule = module {

viewModel { AppViewModel(get(), get(), get(), get(named("IODispatcher")), get()) }
viewModel { AppViewModel(get(), get(), get(), get(), get(named("IODispatcher")), get()) }
viewModel { MainViewModel() }

factory { AuthRepository(get(), get(), get()) }
factory { AuthInteractor(get()) }
factory { Validator() }
viewModel { SignInViewModel(get(), get(), get(), get(), get(), get()) }
viewModel { SignInViewModel(get(), get(), get(), get(), get(), get(), get()) }
viewModel { SignUpViewModel(get(), get(), get(), get(), get()) }
viewModel { RestorePasswordViewModel(get(), get(), get(), get()) }

factory { DashboardRepository(get(), get(),get()) }
factory { DashboardRepository(get(), get(), get()) }
factory { DashboardInteractor(get()) }
viewModel { DashboardViewModel(get(), get(), get(), get(), get(), get(), get()) }

factory { DiscoveryRepository(get(), get()) }
factory { DiscoveryInteractor(get()) }
viewModel { DiscoveryViewModel(get(), get(), get(), get(), get(), get()) }
viewModel { DiscoveryViewModel(get(), get(), get(), get(), get(), get(), get()) }

factory { ProfileRepository(get(), get(), get(), get(), get()) }
factory { ProfileInteractor(get()) }
viewModel { ProfileViewModel(get(), get(), get(), get(named("IODispatcher")), get(), get(), get(), get()) }
viewModel {
ProfileViewModel(
get(),
get(),
get(),
get(),
get(named("IODispatcher")),
get(),
get(),
get(),
get()
)
}
viewModel { (account: Account) -> EditProfileViewModel(get(), get(), get(), get(), account) }
viewModel { VideoSettingsViewModel(get(), get()) }
viewModel { VideoQualityViewModel(get(), get()) }
viewModel { DeleteProfileViewModel(get(), get(), get(), get()) }
viewModel { (username: String) -> AnothersProfileViewModel(get(), get(), username) }

single { CourseRepository(get(), get(), get(),get()) }
single { CourseRepository(get(), get(), get(), get()) }
factory { CourseInteractor(get()) }
viewModel { (courseId: String) -> CourseDetailsViewModel(courseId, get(), get(), get(), get(), get(), get()) }
viewModel { (courseId: String) -> CourseContainerViewModel(courseId, get(), get(), get(), get(), get()) }
viewModel { (courseId: String) -> CourseOutlineViewModel(courseId, get(), get(), get(), get(), get(), get(), get(), get(), get()) }
viewModel { (courseId: String) -> CourseSectionViewModel(get(), get(), get(), get(), get(), get(), get(), get(), courseId) }
viewModel { (courseId: String) ->
CourseDetailsViewModel(
courseId,
get(),
get(),
get(),
get(),
get(),
get()
)
}
viewModel { (courseId: String) ->
CourseContainerViewModel(
courseId,
get(),
get(),
get(),
get(),
get()
)
}
viewModel { (courseId: String) ->
CourseOutlineViewModel(
courseId,
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get()
)
}
viewModel { (courseId: String) ->
CourseSectionViewModel(
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
courseId
)
}
viewModel { (courseId: String) -> CourseUnitContainerViewModel(get(), get(), get(), courseId) }
viewModel { (courseId: String) -> CourseVideoViewModel(courseId, get(), get(), get(), get(), get(), get(), get(), get()) }
viewModel { (courseId: String) ->
CourseVideoViewModel(
courseId,
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get()
)
}
viewModel { (courseId: String) -> VideoViewModel(courseId, get(), get(), get()) }
viewModel { (courseId: String) -> VideoUnitViewModel(courseId, get(), get(), get(), get()) }
viewModel { (courseId: String, blockId: String) -> EncodedVideoUnitViewModel(courseId, blockId, get(), get(), get(), get(), get(), get()) }
viewModel { (courseId: String, blockId: String) ->
EncodedVideoUnitViewModel(
courseId,
blockId,
get(),
get(),
get(),
get(),
get(),
get()
)
}
viewModel { (courseId: String) -> CourseDatesViewModel(courseId, get(), get(), get()) }
viewModel { (courseId:String, handoutsType: String) -> HandoutsViewModel(courseId, get(), handoutsType, get()) }
viewModel { (courseId: String, handoutsType: String) ->
HandoutsViewModel(
courseId,
get(),
handoutsType,
get()
)
}
viewModel { CourseSearchViewModel(get(), get(), get(), get()) }
viewModel { SelectDialogViewModel(get()) }

single { DiscussionRepository(get(), get()) }
factory { DiscussionInteractor(get()) }
viewModel { (courseId: String) -> DiscussionTopicsViewModel(get(), get(), get(), courseId) }
viewModel { (courseId: String, topicId: String, threadType: String) -> DiscussionThreadsViewModel(get(), get(), get(), courseId, topicId, threadType) }
viewModel { (thread: org.openedx.discussion.domain.model.Thread) -> DiscussionCommentsViewModel(get(), get(), get(), thread) }
viewModel { (comment: DiscussionComment) -> DiscussionResponsesViewModel(get(), get(), get(), comment) }
viewModel { (courseId: String, topicId: String, threadType: String) ->
DiscussionThreadsViewModel(
get(),
get(),
get(),
courseId,
topicId,
threadType
)
}
viewModel { (thread: org.openedx.discussion.domain.model.Thread) ->
DiscussionCommentsViewModel(
get(),
get(),
get(),
thread
)
}
viewModel { (comment: DiscussionComment) ->
DiscussionResponsesViewModel(
get(),
get(),
get(),
comment
)
}
viewModel { (courseId: String) -> DiscussionAddThreadViewModel(get(), get(), get(), courseId) }
viewModel { (courseId: String) -> DiscussionSearchThreadViewModel(get(), get(), get(), courseId) }
viewModel { (courseId: String) ->
DiscussionSearchThreadViewModel(
get(),
get(),
get(),
courseId
)
}

viewModel { WhatsNewViewModel(get()) }
}
Loading