Merge branch 'master' of https://github.com/austinhuang0131/instagrabber
This commit is contained in:
commit
38cc804a3f
@ -1,14 +1,13 @@
|
|||||||
package awais.instagrabber.models
|
package awais.instagrabber.models
|
||||||
|
|
||||||
import awais.instagrabber.utils.TextUtils
|
import awais.instagrabber.utils.TextUtils
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
data class HighlightModel(
|
data class HighlightModel(
|
||||||
val title: String?,
|
val title: String? = null,
|
||||||
val id: String,
|
val id: String = "",
|
||||||
val thumbnailUrl: String,
|
val thumbnailUrl: String = "",
|
||||||
val timestamp: Long,
|
val timestamp: Long = 0,
|
||||||
val mediaCount: Int
|
val mediaCount: Int = 0,
|
||||||
) {
|
) {
|
||||||
val dateTime: String
|
val dateTime: String
|
||||||
get() = TextUtils.epochSecondToString(timestamp)
|
get() = TextUtils.epochSecondToString(timestamp)
|
||||||
|
@ -5,14 +5,14 @@ import awais.instagrabber.models.stickers.*
|
|||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
data class StoryModel(
|
data class StoryModel(
|
||||||
val storyMediaId: String?,
|
val storyMediaId: String? = null,
|
||||||
val storyUrl: String?,
|
val storyUrl: String? = null,
|
||||||
var thumbnail: String?,
|
var thumbnail: String? = null,
|
||||||
val itemType: MediaItemType?,
|
val itemType: MediaItemType? = null,
|
||||||
val timestamp: Long,
|
val timestamp: Long = 0,
|
||||||
val username: String?,
|
val username: String? = null,
|
||||||
val userId: Long,
|
val userId: Long = 0,
|
||||||
val canReply: Boolean
|
val canReply: Boolean = false,
|
||||||
) : Serializable {
|
) : Serializable {
|
||||||
var videoUrl: String? = null
|
var videoUrl: String? = null
|
||||||
var tappableShortCode: String? = null
|
var tappableShortCode: String? = null
|
||||||
|
@ -8,8 +8,11 @@ import awais.instagrabber.db.entities.Favorite
|
|||||||
import awais.instagrabber.db.repositories.AccountRepository
|
import awais.instagrabber.db.repositories.AccountRepository
|
||||||
import awais.instagrabber.db.repositories.FavoriteRepository
|
import awais.instagrabber.db.repositories.FavoriteRepository
|
||||||
import awais.instagrabber.managers.DirectMessagesManager
|
import awais.instagrabber.managers.DirectMessagesManager
|
||||||
|
import awais.instagrabber.models.HighlightModel
|
||||||
import awais.instagrabber.models.Resource
|
import awais.instagrabber.models.Resource
|
||||||
|
import awais.instagrabber.models.StoryModel
|
||||||
import awais.instagrabber.models.enums.FavoriteType
|
import awais.instagrabber.models.enums.FavoriteType
|
||||||
|
import awais.instagrabber.repositories.requests.StoryViewerOptions
|
||||||
import awais.instagrabber.repositories.responses.User
|
import awais.instagrabber.repositories.responses.User
|
||||||
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
|
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
|
||||||
import awais.instagrabber.utils.ControlledRunner
|
import awais.instagrabber.utils.ControlledRunner
|
||||||
@ -22,7 +25,7 @@ class ProfileFragmentViewModel(
|
|||||||
state: SavedStateHandle,
|
state: SavedStateHandle,
|
||||||
userRepository: UserRepository,
|
userRepository: UserRepository,
|
||||||
friendshipRepository: FriendshipRepository,
|
friendshipRepository: FriendshipRepository,
|
||||||
storiesRepository: StoriesRepository,
|
private val storiesRepository: StoriesRepository,
|
||||||
mediaRepository: MediaRepository,
|
mediaRepository: MediaRepository,
|
||||||
graphQLRepository: GraphQLRepository,
|
graphQLRepository: GraphQLRepository,
|
||||||
accountRepository: AccountRepository,
|
accountRepository: AccountRepository,
|
||||||
@ -61,27 +64,21 @@ class ProfileFragmentViewModel(
|
|||||||
|
|
||||||
private val profileFetchControlledRunner = ControlledRunner<User?>()
|
private val profileFetchControlledRunner = ControlledRunner<User?>()
|
||||||
val profile: LiveData<Resource<User?>> = currentUserAndStateUsernameLiveData.switchMap {
|
val profile: LiveData<Resource<User?>> = currentUserAndStateUsernameLiveData.switchMap {
|
||||||
val (userResource, stateUsernameResource) = it
|
val (currentUserResource, stateUsernameResource) = it
|
||||||
liveData<Resource<User?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
|
liveData<Resource<User?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
|
||||||
if (userResource.status == Resource.Status.LOADING || stateUsernameResource.status == Resource.Status.LOADING) {
|
if (currentUserResource.status == Resource.Status.LOADING || stateUsernameResource.status == Resource.Status.LOADING) {
|
||||||
emit(Resource.loading(null))
|
emit(Resource.loading(null))
|
||||||
return@liveData
|
return@liveData
|
||||||
}
|
}
|
||||||
val user = userResource.data
|
val currentUser = currentUserResource.data
|
||||||
val stateUsername = stateUsernameResource.data
|
val stateUsername = stateUsernameResource.data
|
||||||
if (stateUsername.isNullOrBlank()) {
|
if (stateUsername.isNullOrBlank()) {
|
||||||
emit(Resource.success(user))
|
emit(Resource.success(currentUser))
|
||||||
return@liveData
|
return@liveData
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
val fetchedUser = profileFetchControlledRunner.cancelPreviousThenRun {
|
val fetchedUser = profileFetchControlledRunner.cancelPreviousThenRun {
|
||||||
return@cancelPreviousThenRun if (user != null) {
|
return@cancelPreviousThenRun fetchUser(currentUser, userRepository, stateUsername, graphQLRepository)
|
||||||
val tempUser = userRepository.getUsernameInfo(stateUsername) // logged in
|
|
||||||
tempUser.friendshipStatus = userRepository.getUserFriendship(tempUser.pk)
|
|
||||||
return@cancelPreviousThenRun tempUser
|
|
||||||
} else {
|
|
||||||
graphQLRepository.fetchUser(stateUsername) // anonymous
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
emit(Resource.success(fetchedUser))
|
emit(Resource.success(fetchedUser))
|
||||||
if (fetchedUser != null) {
|
if (fetchedUser != null) {
|
||||||
@ -94,6 +91,81 @@ class ProfileFragmentViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val storyFetchControlledRunner = ControlledRunner<List<StoryModel>?>()
|
||||||
|
val userStories: LiveData<Resource<List<StoryModel>?>> = profile.switchMap { userResource ->
|
||||||
|
liveData<Resource<List<StoryModel>?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
|
||||||
|
// don't fetch if not logged in
|
||||||
|
if (isLoggedIn.value != true) {
|
||||||
|
emit(Resource.success(null))
|
||||||
|
return@liveData
|
||||||
|
}
|
||||||
|
if (userResource.status == Resource.Status.LOADING) {
|
||||||
|
emit(Resource.loading(null))
|
||||||
|
return@liveData
|
||||||
|
}
|
||||||
|
val user = userResource.data
|
||||||
|
if (user == null) {
|
||||||
|
emit(Resource.success(null))
|
||||||
|
return@liveData
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val fetchedStories = storyFetchControlledRunner.cancelPreviousThenRun { fetchUserStory(user) }
|
||||||
|
emit(Resource.success(fetchedStories))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
emit(Resource.error(e.message, null))
|
||||||
|
Log.e(TAG, "fetching story: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val highlightsFetchControlledRunner = ControlledRunner<List<HighlightModel>?>()
|
||||||
|
val userHighlights: LiveData<Resource<List<HighlightModel>?>> = profile.switchMap { userResource ->
|
||||||
|
liveData<Resource<List<HighlightModel>?>>(context = viewModelScope.coroutineContext + ioDispatcher) {
|
||||||
|
// don't fetch if not logged in
|
||||||
|
if (isLoggedIn.value != true) {
|
||||||
|
emit(Resource.success(null))
|
||||||
|
return@liveData
|
||||||
|
}
|
||||||
|
if (userResource.status == Resource.Status.LOADING) {
|
||||||
|
emit(Resource.loading(null))
|
||||||
|
return@liveData
|
||||||
|
}
|
||||||
|
val user = userResource.data
|
||||||
|
if (user == null) {
|
||||||
|
emit(Resource.success(null))
|
||||||
|
return@liveData
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val fetchedHighlights = highlightsFetchControlledRunner.cancelPreviousThenRun { fetchUserHighlights(user) }
|
||||||
|
emit(Resource.success(fetchedHighlights))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
emit(Resource.error(e.message, null))
|
||||||
|
Log.e(TAG, "fetching story: ", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchUser(
|
||||||
|
currentUser: User?,
|
||||||
|
userRepository: UserRepository,
|
||||||
|
stateUsername: String,
|
||||||
|
graphQLRepository: GraphQLRepository
|
||||||
|
) = if (currentUser != null) {
|
||||||
|
// logged in
|
||||||
|
val tempUser = userRepository.getUsernameInfo(stateUsername)
|
||||||
|
tempUser.friendshipStatus = userRepository.getUserFriendship(tempUser.pk)
|
||||||
|
tempUser
|
||||||
|
} else {
|
||||||
|
// anonymous
|
||||||
|
graphQLRepository.fetchUser(stateUsername)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchUserStory(fetchedUser: User): List<StoryModel> = storiesRepository.getUserStory(
|
||||||
|
StoryViewerOptions.forUser(fetchedUser.pk, fetchedUser.fullName)
|
||||||
|
)
|
||||||
|
|
||||||
|
private suspend fun fetchUserHighlights(fetchedUser: User): List<HighlightModel> = storiesRepository.fetchHighlights(fetchedUser.pk)
|
||||||
|
|
||||||
private suspend fun checkAndInsertFavorite(fetchedUser: User) {
|
private suspend fun checkAndInsertFavorite(fetchedUser: User) {
|
||||||
try {
|
try {
|
||||||
val favorite = favoriteRepository.getFavorite(fetchedUser.username, FavoriteType.USER)
|
val favorite = favoriteRepository.getFavorite(fetchedUser.username, FavoriteType.USER)
|
||||||
|
@ -19,7 +19,7 @@ import org.json.JSONArray
|
|||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class StoriesRepository(private val service: StoriesService) {
|
open class StoriesRepository(private val service: StoriesService) {
|
||||||
|
|
||||||
suspend fun fetch(mediaId: Long): StoryModel {
|
suspend fun fetch(mediaId: Long): StoryModel {
|
||||||
val response = service.fetch(mediaId)
|
val response = service.fetch(mediaId)
|
||||||
@ -99,7 +99,7 @@ class StoriesRepository(private val service: StoriesService) {
|
|||||||
return sort(feedStoryModels)
|
return sort(feedStoryModels)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun fetchHighlights(profileId: Long): List<HighlightModel> {
|
open suspend fun fetchHighlights(profileId: Long): List<HighlightModel> {
|
||||||
val response = service.fetchHighlights(profileId)
|
val response = service.fetchHighlights(profileId)
|
||||||
val highlightsReel = JSONObject(response).getJSONArray("tray")
|
val highlightsReel = JSONObject(response).getJSONArray("tray")
|
||||||
val length = highlightsReel.length()
|
val length = highlightsReel.length()
|
||||||
@ -150,7 +150,7 @@ class StoriesRepository(private val service: StoriesService) {
|
|||||||
return ArchiveFetchResponse(highlightModels, data.getBoolean("more_available"), data.getString("max_id"))
|
return ArchiveFetchResponse(highlightModels, data.getBoolean("more_available"), data.getString("max_id"))
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getUserStory(options: StoryViewerOptions): List<StoryModel> {
|
open suspend fun getUserStory(options: StoryViewerOptions): List<StoryModel> {
|
||||||
val url = buildUrl(options) ?: return emptyList()
|
val url = buildUrl(options) ?: return emptyList()
|
||||||
val response = service.getUserStory(url)
|
val response = service.getUserStory(url)
|
||||||
val isLocOrHashtag = options.type == StoryViewerOptions.Type.LOCATION || options.type == StoryViewerOptions.Type.HASHTAG
|
val isLocOrHashtag = options.type == StoryViewerOptions.Type.LOCATION || options.type == StoryViewerOptions.Type.HASHTAG
|
||||||
|
@ -17,9 +17,7 @@ open class UserServiceAdapter : UserService {
|
|||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getUserFriendship(uid: Long): FriendshipStatus {
|
override suspend fun getUserFriendship(uid: Long): FriendshipStatus = FriendshipStatus()
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun search(timezoneOffset: Float, query: String): UserSearchResponse {
|
override suspend fun search(timezoneOffset: Float, query: String): UserSearchResponse {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
|
@ -11,8 +11,11 @@ import awais.instagrabber.db.entities.Favorite
|
|||||||
import awais.instagrabber.db.repositories.AccountRepository
|
import awais.instagrabber.db.repositories.AccountRepository
|
||||||
import awais.instagrabber.db.repositories.FavoriteRepository
|
import awais.instagrabber.db.repositories.FavoriteRepository
|
||||||
import awais.instagrabber.getOrAwaitValue
|
import awais.instagrabber.getOrAwaitValue
|
||||||
|
import awais.instagrabber.models.HighlightModel
|
||||||
import awais.instagrabber.models.Resource
|
import awais.instagrabber.models.Resource
|
||||||
|
import awais.instagrabber.models.StoryModel
|
||||||
import awais.instagrabber.models.enums.FavoriteType
|
import awais.instagrabber.models.enums.FavoriteType
|
||||||
|
import awais.instagrabber.repositories.requests.StoryViewerOptions
|
||||||
import awais.instagrabber.repositories.responses.FriendshipStatus
|
import awais.instagrabber.repositories.responses.FriendshipStatus
|
||||||
import awais.instagrabber.repositories.responses.User
|
import awais.instagrabber.repositories.responses.User
|
||||||
import awais.instagrabber.webservices.*
|
import awais.instagrabber.webservices.*
|
||||||
@ -279,6 +282,54 @@ internal class ProfileFragmentViewModelTest {
|
|||||||
while (profile.status == Resource.Status.LOADING) {
|
while (profile.status == Resource.Status.LOADING) {
|
||||||
profile = viewModel.profile.getOrAwaitValue()
|
profile = viewModel.profile.getOrAwaitValue()
|
||||||
}
|
}
|
||||||
|
assertEquals(true, viewModel.isFavorite.getOrAwaitValue())
|
||||||
assertTrue(updateFavoriteCalled)
|
assertTrue(updateFavoriteCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
|
@Test
|
||||||
|
fun `should fetch user stories and highlights when logged in`() {
|
||||||
|
val state = SavedStateHandle(
|
||||||
|
mutableMapOf<String, Any?>(
|
||||||
|
"username" to testPublicUser.username
|
||||||
|
)
|
||||||
|
)
|
||||||
|
val testUserStories = listOf(StoryModel())
|
||||||
|
val testUserHighlights = listOf(HighlightModel())
|
||||||
|
val userRepository = object : UserRepository(UserServiceAdapter()) {
|
||||||
|
override suspend fun getUsernameInfo(username: String): User = testPublicUser
|
||||||
|
}
|
||||||
|
val storiesRepository = object : StoriesRepository(StoriesServiceAdapter()) {
|
||||||
|
override suspend fun getUserStory(options: StoryViewerOptions): List<StoryModel> = testUserStories
|
||||||
|
override suspend fun fetchHighlights(profileId: Long): List<HighlightModel> = testUserHighlights
|
||||||
|
}
|
||||||
|
val viewModel = ProfileFragmentViewModel(
|
||||||
|
state,
|
||||||
|
userRepository,
|
||||||
|
FriendshipRepository(FriendshipServiceAdapter()),
|
||||||
|
storiesRepository,
|
||||||
|
MediaRepository(MediaServiceAdapter()),
|
||||||
|
GraphQLRepository(GraphQLServiceAdapter()),
|
||||||
|
AccountRepository(AccountDataSource(AccountDaoAdapter())),
|
||||||
|
FavoriteRepository(FavoriteDataSource(FavoriteDaoAdapter())),
|
||||||
|
coroutineScope.dispatcher,
|
||||||
|
)
|
||||||
|
viewModel.setCurrentUser(Resource.success(User()))
|
||||||
|
assertEquals(true, viewModel.isLoggedIn.getOrAwaitValue())
|
||||||
|
var profile = viewModel.profile.getOrAwaitValue()
|
||||||
|
while (profile.status == Resource.Status.LOADING) {
|
||||||
|
profile = viewModel.profile.getOrAwaitValue()
|
||||||
|
}
|
||||||
|
var userStories = viewModel.userStories.getOrAwaitValue()
|
||||||
|
while (userStories.status == Resource.Status.LOADING) {
|
||||||
|
userStories = viewModel.userStories.getOrAwaitValue()
|
||||||
|
}
|
||||||
|
assertEquals(testUserStories, userStories.data)
|
||||||
|
var userHighlights = viewModel.userHighlights.getOrAwaitValue()
|
||||||
|
while (userHighlights.status == Resource.Status.LOADING) {
|
||||||
|
userHighlights = viewModel.userHighlights.getOrAwaitValue()
|
||||||
|
}
|
||||||
|
assertEquals(testUserHighlights, userHighlights.data)
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user