diff --git a/modules/common_resource/src/main/res/menu/activity_user_menu.xml b/modules/common_resource/src/main/res/menu/activity_user_menu.xml index 491a0b5d4a..49674e3c2e 100644 --- a/modules/common_resource/src/main/res/menu/activity_user_menu.xml +++ b/modules/common_resource/src/main/res/menu/activity_user_menu.xml @@ -45,4 +45,9 @@ + + \ No newline at end of file diff --git a/modules/features/user/src/main/java/net/pantasystem/milktea/user/activity/FollowFollowerActivity.kt b/modules/features/user/src/main/java/net/pantasystem/milktea/user/activity/FollowFollowerActivity.kt index 88dd12b631..d00e002e28 100644 --- a/modules/features/user/src/main/java/net/pantasystem/milktea/user/activity/FollowFollowerActivity.kt +++ b/modules/features/user/src/main/java/net/pantasystem/milktea/user/activity/FollowFollowerActivity.kt @@ -16,7 +16,6 @@ import net.pantasystem.milktea.user.UserCardActionHandler import net.pantasystem.milktea.user.compose.screen.FollowFollowerRoute import net.pantasystem.milktea.user.viewmodel.FollowFollowerViewModel import net.pantasystem.milktea.user.viewmodel.ToggleFollowViewModel -import net.pantasystem.milktea.user.viewmodel.UserDetailViewModel import net.pantasystem.milktea.user.viewmodel.provideFactory import javax.inject.Inject @@ -42,8 +41,6 @@ class FollowFollowerActivity : AppCompatActivity() { } } - @Inject - lateinit var assistedFactory: UserDetailViewModel.ViewModelAssistedFactory @Inject lateinit var applyTheme: ApplyTheme diff --git a/modules/features/user/src/main/java/net/pantasystem/milktea/user/activity/UserDetailActivity.kt b/modules/features/user/src/main/java/net/pantasystem/milktea/user/activity/UserDetailActivity.kt index 9ed6945999..ca0b47eaeb 100644 --- a/modules/features/user/src/main/java/net/pantasystem/milktea/user/activity/UserDetailActivity.kt +++ b/modules/features/user/src/main/java/net/pantasystem/milktea/user/activity/UserDetailActivity.kt @@ -51,12 +51,12 @@ import net.pantasystem.milktea.user.activity.binder.UserDetailActivityMenuBinder import net.pantasystem.milktea.user.databinding.ActivityUserDetailBinding import net.pantasystem.milktea.user.nickname.EditNicknameDialog import net.pantasystem.milktea.user.profile.ConfirmUserBlockDialog +import net.pantasystem.milktea.user.profile.ProfileAccountSwitchDialog import net.pantasystem.milktea.user.profile.UserProfileFieldListAdapter import net.pantasystem.milktea.user.profile.mute.SpecifyMuteExpiredAtDialog import net.pantasystem.milktea.user.reaction.UserReactionsFragment import net.pantasystem.milktea.user.viewmodel.UserDetailTabType import net.pantasystem.milktea.user.viewmodel.UserDetailViewModel -import net.pantasystem.milktea.user.viewmodel.provideFactory import javax.inject.Inject class UserDetailNavigationImpl @Inject constructor( @@ -80,11 +80,11 @@ class UserDetailNavigationImpl @Inject constructor( @AndroidEntryPoint class UserDetailActivity : AppCompatActivity() { companion object { - private const val EXTRA_USER_ID = + internal const val EXTRA_USER_ID = "net.pantasystem.milktea.user.activity.UserDetailActivity.EXTRA_USER_ID" - private const val EXTRA_USER_NAME = + internal const val EXTRA_USER_NAME = "net.pantasystem.milktea.user.activity.UserDetailActivity.EXTRA_USER_NAME" - private const val EXTRA_ACCOUNT_ID = + internal const val EXTRA_ACCOUNT_ID = "jp.panta.misskeyandroiclient.UserDetailActivity.EXTRA_ACCOUNT_ID" const val EXTRA_IS_MAIN_ACTIVE = "jp.panta.misskeyandroidclient.EXTRA_IS_MAIN_ACTIVE" @@ -104,8 +104,6 @@ class UserDetailActivity : AppCompatActivity() { } } - @Inject - lateinit var assistedFactory: UserDetailViewModel.ViewModelAssistedFactory @Inject lateinit var accountStore: AccountStore @@ -117,25 +115,27 @@ class UserDetailActivity : AppCompatActivity() { lateinit var searchNavigation: SearchNavigation - @ExperimentalCoroutinesApi - val mViewModel: UserDetailViewModel by viewModels { - val remoteUserId: String? = intent.getStringExtra(EXTRA_USER_ID) - val accountId: Long = intent.getLongExtra(EXTRA_ACCOUNT_ID, -1) - if (!(remoteUserId == null || accountId == -1L)) { - val userId = User.Id(accountId, remoteUserId) - return@viewModels UserDetailViewModel.provideFactory(assistedFactory, userId) - } - val userName = intent.data?.getQueryParameter("userName") - ?: intent.getStringExtra(EXTRA_USER_NAME) - ?: intent.data?.path?.let { path -> - if (path.startsWith("/")) { - path.substring(1, path.length) - } else { - path - } - } - return@viewModels UserDetailViewModel.provideFactory(assistedFactory, userName!!) - } +// @ExperimentalCoroutinesApi +// val mViewModel: UserDetailViewModel by viewModels { +// val remoteUserId: String? = intent.getStringExtra(EXTRA_USER_ID) +// val accountId: Long = intent.getLongExtra(EXTRA_ACCOUNT_ID, -1) +// if (!(remoteUserId == null || accountId == -1L)) { +// val userId = User.Id(accountId, remoteUserId) +// return@viewModels UserDetailViewModel.provideFactory(assistedFactory, userId) +// } +// val userName = intent.data?.getQueryParameter("userName") +// ?: intent.getStringExtra(EXTRA_USER_NAME) +// ?: intent.data?.path?.let { path -> +// if (path.startsWith("/")) { +// path.substring(1, path.length) +// } else { +// path +// } +// } +// return@viewModels UserDetailViewModel.provideFactory(assistedFactory, userName!!) +// } + + private val mViewModel: UserDetailViewModel by viewModels() private var mUserId: User.Id? = null @@ -338,7 +338,6 @@ class UserDetailActivity : AppCompatActivity() { } - @OptIn(ExperimentalCoroutinesApi::class) override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.activity_user_menu, menu) @@ -413,6 +412,9 @@ class UserDetailActivity : AppCompatActivity() { ) ) } + R.id.nav_switch_account -> { + ProfileAccountSwitchDialog().show(supportFragmentManager, "switchAccountDialog") + } else -> return false } diff --git a/modules/features/user/src/main/java/net/pantasystem/milktea/user/profile/ProfileAccountSwitchDialog.kt b/modules/features/user/src/main/java/net/pantasystem/milktea/user/profile/ProfileAccountSwitchDialog.kt new file mode 100644 index 0000000000..8380e7297b --- /dev/null +++ b/modules/features/user/src/main/java/net/pantasystem/milktea/user/profile/ProfileAccountSwitchDialog.kt @@ -0,0 +1,70 @@ +package net.pantasystem.milktea.user.profile + +import android.app.Dialog +import android.os.Bundle +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.activityViewModels +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.google.android.material.composethemeadapter.MdcTheme +import dagger.hilt.android.AndroidEntryPoint +import net.pantasystem.milktea.common_android_ui.account.AccountSwitchingDialogLayout +import net.pantasystem.milktea.common_navigation.AccountSettingNavigation +import net.pantasystem.milktea.common_navigation.AuthorizationArgs +import net.pantasystem.milktea.common_navigation.AuthorizationNavigation +import net.pantasystem.milktea.common_navigation.UserDetailNavigation +import net.pantasystem.milktea.common_navigation.UserDetailNavigationArgs +import net.pantasystem.milktea.user.viewmodel.UserDetailViewModel +import javax.inject.Inject + +@AndroidEntryPoint +class ProfileAccountSwitchDialog : BottomSheetDialogFragment() { + @Inject + lateinit var authorizationNavigation: AuthorizationNavigation + + @Inject + lateinit var userDetailNavigation: UserDetailNavigation + + @Inject + lateinit var accountSettingNavigation: AccountSettingNavigation + + private val userDetailViewModel: UserDetailViewModel by activityViewModels() + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return super.onCreateDialog(savedInstanceState).apply { + val view = ComposeView(requireContext()).apply { + setContent { + MdcTheme { + val uiState by userDetailViewModel.accountUiState.collectAsState() + AccountSwitchingDialogLayout( + uiState = uiState, + onSettingButtonClicked = { + startActivity(accountSettingNavigation.newIntent(Unit)) + dismiss() + }, + onAvatarIconClicked = { accountInfo -> + startActivity( + userDetailNavigation.newIntent(UserDetailNavigationArgs.UserName(accountInfo.user?.let { + "@${it.userName}@${it.host}" + } ?: "@${accountInfo.account.userName}@${accountInfo.account.getHost()}")) + ) + dismiss() + }, + onAccountClicked = { + userDetailViewModel.setCurrentAccount(it.account.accountId) + dismiss() + }, + onAddAccountButtonClicked = { + requireActivity().startActivity(authorizationNavigation.newIntent( + AuthorizationArgs.New)) + dismiss() + } + ) + } + } + } + setContentView(view) + } + } +} \ No newline at end of file diff --git a/modules/features/user/src/main/java/net/pantasystem/milktea/user/viewmodel/UserDetailViewModel.kt b/modules/features/user/src/main/java/net/pantasystem/milktea/user/viewmodel/UserDetailViewModel.kt index 8942672e17..95ff624032 100644 --- a/modules/features/user/src/main/java/net/pantasystem/milktea/user/viewmodel/UserDetailViewModel.kt +++ b/modules/features/user/src/main/java/net/pantasystem/milktea/user/viewmodel/UserDetailViewModel.kt @@ -1,13 +1,11 @@ package net.pantasystem.milktea.user.viewmodel import androidx.annotation.StringRes +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope -import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory -import dagger.assisted.AssistedInject +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch @@ -21,12 +19,15 @@ import net.pantasystem.milktea.common.mapCancellableCatching import net.pantasystem.milktea.common.runCancellableCatching import net.pantasystem.milktea.common_android.eventbus.EventBus import net.pantasystem.milktea.common_android.resource.StringSource +import net.pantasystem.milktea.common_android_ui.account.viewmodel.AccountViewModelUiStateHelper import net.pantasystem.milktea.model.account.Account import net.pantasystem.milktea.model.account.AccountRepository import net.pantasystem.milktea.model.account.page.Pageable import net.pantasystem.milktea.model.account.page.PageableTemplate +import net.pantasystem.milktea.model.ap.ApResolverService import net.pantasystem.milktea.model.instance.FeatureEnables import net.pantasystem.milktea.model.instance.FeatureType +import net.pantasystem.milktea.model.instance.InstanceInfoService import net.pantasystem.milktea.model.user.* import net.pantasystem.milktea.model.user.block.BlockRepository import net.pantasystem.milktea.model.user.mute.CreateMute @@ -35,8 +36,11 @@ import net.pantasystem.milktea.model.user.nickname.DeleteNicknameUseCase import net.pantasystem.milktea.model.user.nickname.UpdateNicknameUseCase import net.pantasystem.milktea.model.user.renote.mute.RenoteMuteRepository import net.pantasystem.milktea.user.R +import net.pantasystem.milktea.user.activity.UserDetailActivity +import javax.inject.Inject -class UserDetailViewModel @AssistedInject constructor( +@HiltViewModel +class UserDetailViewModel @Inject constructor( private val deleteNicknameUseCase: DeleteNicknameUseCase, private val updateNicknameUseCase: UpdateNicknameUseCase, private val accountStore: AccountStore, @@ -47,48 +51,76 @@ class UserDetailViewModel @AssistedInject constructor( private val muteRepository: MuteRepository, userDataSource: UserDataSource, loggerFactory: Logger.Factory, + instanceInfoService: InstanceInfoService, private val userRepository: UserRepository, private val featureEnables: FeatureEnables, private val toggleFollowUseCase: ToggleFollowUseCase, - @Assisted val userId: User.Id?, - @Assisted private val fqdnUserName: String?, + private val apResolverService: ApResolverService, + private val savedStateHandle: SavedStateHandle, ) : ViewModel() { - @AssistedFactory - interface ViewModelAssistedFactory { - fun create(userId: User.Id?, fqdnUserName: String?): UserDetailViewModel - } - companion object; private val logger = loggerFactory.create("UserDetailViewModel") - private val currentAccountId = MutableStateFlow(userId?.accountId) + + // private val currentAccountId = MutableStateFlow(userId?.accountId) + private val userId = + savedStateHandle.getStateFlow(UserDetailActivity.EXTRA_USER_ID, null) + private val specifiedAccountId = + savedStateHandle.getStateFlow(UserDetailActivity.EXTRA_ACCOUNT_ID, null) + @OptIn(ExperimentalCoroutinesApi::class) - val currentAccount = currentAccountId.flatMapLatest { accountId -> + val currentAccount = specifiedAccountId.flatMapLatest { accountId -> accountStore.state.map { state -> accountId?.let { state.get(it) } ?: state.currentAccount } - }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) + }.stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(5_000), + null, + ) + + private val fqdnUserName = + savedStateHandle.getStateFlow(UserDetailActivity.EXTRA_USER_NAME, null) private val _errors = MutableSharedFlow(extraBufferCapacity = 100) val errors = _errors.asSharedFlow() + private val userProfileArgType = combine( + userId, + fqdnUserName, + currentAccount, + ) { userId, fqdnUserName, currentAccount -> + when { + userId != null && currentAccount != null -> { + UserProfileArgType.UserId(User.Id(currentAccount.accountId, userId)) + } - @OptIn(ExperimentalCoroutinesApi::class) - val userState = when { - userId != null -> { - userDataSource.observe(userId) - } - fqdnUserName != null -> { - currentAccount.filterNotNull().flatMapLatest { - userDataSource.observe(it.accountId, fqdnUserName) + fqdnUserName != null && currentAccount != null -> { + UserProfileArgType.FqdnUserName(fqdnUserName, currentAccount) + } + + else -> { + UserProfileArgType.None } } - else -> { - throw IllegalArgumentException() + }.stateIn(viewModelScope, SharingStarted.Lazily, UserProfileArgType.None) + + + @OptIn(ExperimentalCoroutinesApi::class) + val userState = userProfileArgType.flatMapLatest { + when (val type = it) { + is UserProfileArgType.FqdnUserName -> { + userDataSource.observe(type.currentAccount.accountId, type.fqdnUserName) + } + + UserProfileArgType.None -> flowOf(null) + is UserProfileArgType.UserId -> { + userDataSource.observe(type.userId) + } } }.mapNotNull { it as? User.Detail @@ -97,6 +129,7 @@ class UserDetailViewModel @AssistedInject constructor( _errors.tryEmit(it) }.stateIn(viewModelScope, SharingStarted.Lazily, null) + val user = userState.asLiveData() val isMine = combine(userState, currentAccount) { userState, account -> @@ -112,7 +145,10 @@ class UserDetailViewModel @AssistedInject constructor( val registrationDate = userState.map { it?.info?.createdAt?.toLocalDateTime(TimeZone.currentSystemDefault())?.date }.filterNotNull().map { - StringSource(R.string.user_registration_date, "${it.year}/${it.monthNumber}/${it.dayOfMonth}") + StringSource( + R.string.user_registration_date, + "${it.year}/${it.monthNumber}/${it.dayOfMonth}" + ) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), null) val tabTypes = combine( @@ -121,12 +157,12 @@ class UserDetailViewModel @AssistedInject constructor( val isEnableGallery = featureEnables.isEnable(account.normalizedInstanceUri, FeatureType.Gallery) val isPublicReaction = featureEnables.isEnable( - account.normalizedInstanceUri, - FeatureType.UserReactionHistory - ) && (user.info.isPublicReactions || user.id == User.Id( - account.accountId, account.remoteId - )) - when(account.instanceType) { + account.normalizedInstanceUri, + FeatureType.UserReactionHistory + ) && (user.info.isPublicReactions || user.id == User.Id( + account.accountId, account.remoteId + )) + when (account.instanceType) { Account.InstanceType.MISSKEY -> { listOfNotNull( UserDetailTabType.UserTimeline(user.id), @@ -140,6 +176,7 @@ class UserDetailViewModel @AssistedInject constructor( if (isPublicReaction) UserDetailTabType.Reactions(user.id) else null, ) } + Account.InstanceType.MASTODON, Account.InstanceType.PLEROMA -> { listOf( UserDetailTabType.MastodonUserTimeline(user.id), @@ -154,6 +191,14 @@ class UserDetailViewModel @AssistedInject constructor( logger.error("ユーザープロフィールのタブの取得に失敗", it) }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList()) + val accountUiState = AccountViewModelUiStateHelper( + currentAccount, + accountStore, + userDataSource, + instanceInfoService, + viewModelScope, + ).uiState + @OptIn(ExperimentalCoroutinesApi::class) val renoteMuteState = userState.filterNotNull().flatMapLatest { renoteMuteRepository.observeOne(it.id) @@ -163,15 +208,7 @@ class UserDetailViewModel @AssistedInject constructor( val showFollows = EventBus() init { - require(userId != null || fqdnUserName != null) { - "userIdかfqdnUserNameのいずれかが指定されている必要があります。" - } sync() - accountStore.observeCurrentAccount.onEach { - if (userId == null) { - currentAccountId.value = it?.accountId - } - }.launchIn(viewModelScope) } @@ -227,7 +264,7 @@ class UserDetailViewModel @AssistedInject constructor( fun unmute() { viewModelScope.launch { userState.value?.let { user -> - muteRepository.delete(user.id).mapCancellableCatching{ + muteRepository.delete(user.id).mapCancellableCatching { userRepository.sync(user.id).getOrThrow() }.onFailure { logger.error("unmute", e = it) @@ -338,52 +375,72 @@ class UserDetailViewModel @AssistedInject constructor( } } + fun setCurrentAccount(accountId: Long) { + viewModelScope.launch { + accountRepository.get(accountId).mapCancellableCatching { + it to apResolverService.resolve(getUserId(), accountId).getOrThrow() + }.onSuccess { (account, resolved) -> + savedStateHandle[UserDetailActivity.EXTRA_USER_ID] = resolved.id.id + savedStateHandle[UserDetailActivity.EXTRA_ACCOUNT_ID] = resolved.id.accountId + accountStore.setCurrent(account) + }.onFailure { + logger.error("setCurrentAccount failed", it) + _errors.tryEmit(it) + } + } + } + private suspend fun findUser(): User { return userRepository.find(getUserId()) } private suspend fun getUserId(): User.Id { - if (userId != null) { - return userId - } + val strUserId = savedStateHandle.get(UserDetailActivity.EXTRA_USER_ID) + val specifiedAccountId = savedStateHandle.get(UserDetailActivity.EXTRA_ACCOUNT_ID) + val fqdnUserName = savedStateHandle.get(UserDetailActivity.EXTRA_USER_NAME) + val currentAccount = specifiedAccountId?.let { + accountRepository.get(it).getOrThrow() + } ?: accountRepository.getCurrentAccount().getOrThrow() + val argType = when { + strUserId != null -> { + UserProfileArgType.UserId(User.Id(currentAccount.accountId, strUserId)) + } + + fqdnUserName != null -> { + UserProfileArgType.FqdnUserName(fqdnUserName, currentAccount) + } - val account = currentAccount.value ?: accountRepository.getCurrentAccount().getOrThrow() - if (fqdnUserName != null) { - val (userName, host) = Acct(fqdnUserName).let { - it.userName to it.host + else -> { + UserProfileArgType.None } - return userRepository.findByUserName(account.accountId, userName, host).id } - throw IllegalStateException() - } -} -@Suppress("UNCHECKED_CAST") -fun UserDetailViewModel.Companion.provideFactory( - assistedFactory: UserDetailViewModel.ViewModelAssistedFactory, userId: User.Id -): ViewModelProvider.Factory = object : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - return assistedFactory.create(userId, null) as T - } -} + return when (argType) { + is UserProfileArgType.FqdnUserName -> { + val (userName, host) = Acct(argType.fqdnUserName).let { + it.userName to it.host + } + userRepository.findByUserName(currentAccount.accountId, userName, host).id + } + + is UserProfileArgType.UserId -> { + argType.userId + } + + UserProfileArgType.None -> throw IllegalStateException() + } -@Suppress("UNCHECKED_CAST") -fun UserDetailViewModel.Companion.provideFactory( - assistedFactory: UserDetailViewModel.ViewModelAssistedFactory, - fqdnUserName: String, -): ViewModelProvider.Factory = object : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - return assistedFactory.create(null, fqdnUserName) as T } } sealed class UserDetailTabType( - @StringRes val title: Int + @StringRes val title: Int, ) { data class UserTimeline(val userId: User.Id) : UserDetailTabType(R.string.post) - data class UserTimelineWithReplies(val userId: User.Id) : UserDetailTabType(R.string.notes_and_replies) + data class UserTimelineWithReplies(val userId: User.Id) : + UserDetailTabType(R.string.notes_and_replies) data class UserTimelineOnlyPosts(val userId: User.Id) : UserDetailTabType(R.string.post_only) @@ -395,9 +452,21 @@ sealed class UserDetailTabType( data class Media(val userId: User.Id) : UserDetailTabType(R.string.media) data class MastodonUserTimeline(val userId: User.Id) : UserDetailTabType(R.string.post) - data class MastodonUserTimelineWithReplies(val userId: User.Id) : UserDetailTabType(R.string.notes_and_replies) + data class MastodonUserTimelineWithReplies(val userId: User.Id) : + UserDetailTabType(R.string.notes_and_replies) - data class MastodonUserTimelineOnlyPosts(val userId: User.Id) : UserDetailTabType(R.string.post_only) + data class MastodonUserTimelineOnlyPosts(val userId: User.Id) : + UserDetailTabType(R.string.post_only) data class MastodonMedia(val userId: User.Id) : UserDetailTabType(R.string.media) +} + +sealed interface UserProfileArgType { + data class UserId(val userId: User.Id) : UserProfileArgType + + data class FqdnUserName(val fqdnUserName: String, val currentAccount: Account) : + UserProfileArgType + + object None : UserProfileArgType + } \ No newline at end of file diff --git a/modules/model/src/main/java/net/pantasystem/milktea/model/ap/ApResolverService.kt b/modules/model/src/main/java/net/pantasystem/milktea/model/ap/ApResolverService.kt index 9a61f82f77..80308997c6 100644 --- a/modules/model/src/main/java/net/pantasystem/milktea/model/ap/ApResolverService.kt +++ b/modules/model/src/main/java/net/pantasystem/milktea/model/ap/ApResolverService.kt @@ -1,8 +1,11 @@ package net.pantasystem.milktea.model.ap import net.pantasystem.milktea.common.mapCancellableCatching +import net.pantasystem.milktea.common.runCancellableCatching +import net.pantasystem.milktea.model.account.AccountRepository import net.pantasystem.milktea.model.notes.Note import net.pantasystem.milktea.model.notes.NoteRepository +import net.pantasystem.milktea.model.user.User import net.pantasystem.milktea.model.user.UserRepository import javax.inject.Inject @@ -10,6 +13,7 @@ class ApResolverService @Inject constructor( private val apResolverRepository: ApResolverRepository, private val noteRepository: NoteRepository, private val userRepository: UserRepository, + private val accountRepository: AccountRepository, ) { suspend fun resolve(noteId: Note.Id, resolveToAccountId: Long): Result { @@ -25,5 +29,20 @@ class ApResolverService @Inject constructor( } } - + suspend fun resolve(userId: User.Id, resolveToAccountId: Long): Result = runCancellableCatching { + val user = (userRepository.find(userId, true) as User.Detail) + val resolveAccount = accountRepository.get(resolveToAccountId).getOrThrow() + if (resolveAccount.getHost() == user.host) { + return@runCancellableCatching userRepository.findByUserName(resolveToAccountId, user.userName, user.host) + } + val uri = user.getRemoteProfileUrl( + accountRepository.get(userId.accountId).getOrThrow() + ) + apResolverRepository.resolve(resolveToAccountId, uri).mapCancellableCatching { + when (it) { + is ApResolver.TypeNote -> throw IllegalStateException("Cannot resolve note") + is ApResolver.TypeUser -> it.user + } + }.getOrThrow() + } } \ No newline at end of file