聊天-注册登录

WelcomeFragment

服务条款页面是WelcomeFragment

在这里插入图片描述

EnterPhoneNumberFragment

电话号码输入页面是EnterPhoneNumberFragment:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

开屏页,手机号,验证码,PIN设置,注册,登录

安装完成后第一次进入app后显示的闪屏页面是PassphraseCreateActivity,布局是R.layout.fragment_registration_blank

注册完成页面RegistrationCompleteFragment和WelcomeFragment欢迎页面都会显示闪屏页面:R.layout.fragment_registration_blank

WelcomeFragment:这个页面有两个功能,闪屏页面以及用户协议确认页面

RegistrationNavigationActivity管理着注册登录流程相关的多个fragment:

  • WelcomeFragment
  • EnterPhoneNumberFragment
  • CountryPickerFragment
  • EnterCodeFragment
  • RegistrationCompleteFragment
  • 等等

具体见:registration.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/signup"
    app:startDestination="@id/welcomeFragment">

    <fragment
        android:id="@+id/welcomeFragment"
        android:name="org.thoughtcrime.securesms.registration.fragments.WelcomeFragment"
        android:label="fragment_welcome"
        tools:layout="@layout/fragment_registration_welcome">

        <action
            android:id="@+id/action_restore"
            app:destination="@id/restoreBackupFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />

        <action
            android:id="@+id/action_skip_restore"
            app:destination="@id/enterPhoneNumberFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />

    </fragment>

    <fragment
        android:id="@+id/enterPhoneNumberFragment"
        android:name="org.thoughtcrime.securesms.registration.fragments.EnterPhoneNumberFragment"
        android:label="fragment_enter_phone_number"
        tools:layout="@layout/fragment_registration_enter_phone_number">

        <action
            android:id="@+id/action_pickCountry"
            app:destination="@id/countryPickerFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:launchSingleTop="true"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />

        <action
            android:id="@+id/action_enterVerificationCode"
            app:destination="@id/enterCodeFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />

        <action
            android:id="@+id/action_requestCaptcha"
            app:destination="@id/captchaFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />

    </fragment>

    <fragment
        android:id="@+id/countryPickerFragment"
        android:name="org.thoughtcrime.securesms.registration.fragments.CountryPickerFragment"
        android:label="fragment_country_picker"
        tools:layout="@layout/fragment_registration_country_picker">

        <action
            android:id="@+id/action_countrySelected"
            app:popUpTo="@id/countryPickerFragment"
            app:popUpToInclusive="true" />

    </fragment>

    <fragment
        android:id="@+id/enterCodeFragment"
        android:name="org.thoughtcrime.securesms.registration.fragments.EnterCodeFragment"
        android:label="fragment_enter_code"
        tools:layout="@layout/fragment_registration_enter_code">

        <action
            android:id="@+id/action_requireKbsLockPin"
            app:destination="@id/registrationLockFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim"
            app:popUpTo="@+id/welcomeFragment"
            app:popUpToInclusive="true" />

        <action
            android:id="@+id/action_wrongNumber"
            app:popUpTo="@id/enterCodeFragment"
            app:popUpToInclusive="true" />

        <action
            android:id="@+id/action_requestCaptcha"
            app:destination="@id/captchaFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim" />

        <action
            android:id="@+id/action_successfulRegistration"
            app:destination="@id/registrationCompletePlaceHolderFragment"
            app:popUpTo="@+id/welcomeFragment"
            app:popUpToInclusive="true" />
        <action
            android:id="@+id/action_accountLocked"
            app:destination="@id/accountLockedFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim"
            app:popUpTo="@+id/welcomeFragment"
            app:popUpToInclusive="true" />

    </fragment>

    <fragment
        android:id="@+id/registrationLockFragment"
        android:name="org.thoughtcrime.securesms.registration.fragments.RegistrationLockFragment"
        android:label="fragment_kbs_lock"
        tools:layout="@layout/fragment_registration_lock">

        <action
            android:id="@+id/action_successfulRegistration"
            app:destination="@id/registrationCompletePlaceHolderFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim"
            app:popUpTo="@+id/welcomeFragment"
            app:popUpToInclusive="true" />

        <action
            android:id="@+id/action_accountLocked"
            app:destination="@id/accountLockedFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim"
            app:popUpTo="@+id/welcomeFragment"
            app:popUpToInclusive="true" />

        <argument
            android:name="timeRemaining"
            app:argType="long" />

        <argument
            android:name="isV1RegistrationLock"
            app:argType="boolean" />

    </fragment>

    <fragment
        android:id="@+id/accountLockedFragment"
        android:name="org.thoughtcrime.securesms.registration.fragments.AccountLockedFragment"
        android:label="fragment_account_locked"
        tools:layout="@layout/account_locked_fragment"/>

    <fragment
        android:id="@+id/captchaFragment"
        android:name="org.thoughtcrime.securesms.registration.fragments.CaptchaFragment"
        android:label="fragment_captcha"
        tools:layout="@layout/fragment_registration_captcha">

        <action
            android:id="@+id/action_captchaComplete"
            app:popUpTo="@id/captchaFragment"
            app:popUpToInclusive="true" />

    </fragment>

    <fragment
        android:id="@+id/restoreBackupFragment"
        android:name="org.thoughtcrime.securesms.registration.fragments.RestoreBackupFragment"
        android:label="fragment_restore_backup"
        tools:layout="@layout/fragment_registration_restore_backup">

        <action
            android:id="@+id/action_backupRestored"
            app:destination="@id/enterPhoneNumberFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim"
            app:popUpTo="@id/restoreBackupFragment"
            app:popUpToInclusive="true" />

        <action
            android:id="@+id/action_skip"
            app:destination="@id/enterPhoneNumberFragment"
            app:enterAnim="@anim/slide_from_end"
            app:exitAnim="@anim/slide_to_start"
            app:popEnterAnim="@anim/slide_from_start"
            app:popExitAnim="@anim/slide_to_end" />

        <action
            android:id="@+id/action_noBackupFound"
            app:destination="@id/enterPhoneNumberFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim"
            app:popUpTo="@id/restoreBackupFragment"
            app:popUpToInclusive="true" />

        <action
            android:id="@+id/action_skip_no_return"
            app:destination="@id/enterPhoneNumberFragment"
            app:enterAnim="@anim/nav_default_enter_anim"
            app:exitAnim="@anim/nav_default_exit_anim"
            app:popEnterAnim="@anim/nav_default_pop_enter_anim"
            app:popExitAnim="@anim/nav_default_pop_exit_anim"
            app:popUpTo="@+id/restoreBackupFragment"
            app:popUpToInclusive="true" />

    </fragment>

    <fragment
        android:id="@+id/registrationCompletePlaceHolderFragment"
        android:name="org.thoughtcrime.securesms.registration.fragments.RegistrationCompleteFragment"
        android:label="fragment_registration_complete_place_holder"
        tools:layout="@layout/fragment_registration_blank" />

</navigation>

EnterPhoneNumberFragment 输入手机号,点击NEXT按钮-> handleRegister() -> handleRequestVerification()验证手机号 -> 根据需要判断是否要进行图片验证CaptchaFragment -> EnterPhoneNumberFragment#requestVerificationCode()请求验证码 -> RegistrationCodeRequest#requestSmsVerificationCode

EnterCodeFragment -> RegistrationCompleteFragment# onViewCreated()

//RegistrationCompleteFragment.java

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    FragmentActivity activity = requireActivity();

    if (SignalStore.storageServiceValues().needsAccountRestore()) {
      activity.startActivity(new Intent(activity, PinRestoreActivity.class));
    } else if (!isReregister()) {
      final Intent main    = new Intent(activity, MainActivity.class);
      final Intent profile = EditProfileActivity.getIntent(activity, false);

      Intent kbs = CreateKbsPinActivity.getIntentForPinCreate(requireContext());
      activity.startActivity(chainIntents(chainIntents(profile, kbs), main));
    }

    activity.finish();
    ActivityNavigator.applyPopAnimationsToPendingTransition(activity);
  }

会进行判断,如果还没有设置Pin,则跳到Pin设置页面PinRestoreActivity,PinRestoreEntryFragment;pinButton就是CONTINUE按钮,onPinSubmitted()

否则如果设置了Pin,且不是注册的话则启动MainActivity

点击CONTINUE按钮执行onPinSubmitted()时会根据FCM token来执行不同的逻辑

//PinRestoreViewModel.java

void onPinSubmitted(@NonNull String pin, @NonNull PinKeyboardType pinKeyboardType) {
    int trimmedLength = pin.replace(" ", "").length();

    if (trimmedLength == 0) {
        event.postValue(Event.EMPTY_PIN);
        return;
    }

    if (trimmedLength < KbsConstants.MINIMUM_PIN_LENGTH) {
        event.postValue(Event.PIN_TOO_SHORT);
        return;
    }

    if (tokenData != null) {
        repo.submitPin(pin, tokenData, result -> {

            switch (result.getResult()) {
                case SUCCESS:
                    SignalStore.pinValues().setKeyboardType(pinKeyboardType);
                    SignalStore.storageServiceValues().setNeedsAccountRestore(false);
                    event.postValue(Event.SUCCESS);
                    break;
                case LOCKED:
                    event.postValue(Event.PIN_LOCKED);
                    break;
                case INCORRECT:
                    event.postValue(Event.PIN_INCORRECT);
                    updateTokenData(result.getTokenData(), true);
                    break;
                case NETWORK_ERROR:
                    event.postValue(Event.NETWORK_ERROR);
                    break;
            }
        });
    } else {
        repo.getToken(token -> {
            if (token.isPresent()) {
                updateTokenData(token.get(), false);
                onPinSubmitted(pin, pinKeyboardType);
            } else {
                event.postValue(Event.NETWORK_ERROR);
            }
        });
    }
}

进入这个页面的时候就会获取token

public class PinRestoreViewModel extends ViewModel {

    private final PinRestoreRepository repo;
    private final DefaultValueLiveData<TriesRemaining> triesRemaining;
    private final SingleLiveEvent<Event> event;

    private volatile PinRestoreRepository.TokenData tokenData;

    public PinRestoreViewModel() {
        this.repo = new PinRestoreRepository();
        this.triesRemaining = new DefaultValueLiveData<>(new TriesRemaining(10, false));
        this.event = new SingleLiveEvent<>();

        repo.getToken(token -> {
            if (token.isPresent()) {
                updateTokenData(token.get(), false);
            } else {
                event.postValue(Event.NETWORK_ERROR);
            }
        });
    }

Megaphone

首页的Megaphone提示框:
在这里插入图片描述

相关类是Megaphone

RegistrationCompleteFragment

注册完成之后进入:RegistrationCompleteFragment 页面

注册时的输入手机号后请求验证码和验证码校验接口都需要带上Credentials:

//RegistrationService.java

public final class RegistrationService {

  private final Credentials credentials;

  private RegistrationService(@NonNull Credentials credentials) {
    this.credentials = credentials;
  }

  public static RegistrationService getInstance(@NonNull String e164number, @NonNull String password) {
    return new RegistrationService(new Credentials(e164number, password));
  }

  /**
   * See {@link RegistrationCodeRequest}.
   */
  public void requestVerificationCode(@NonNull Activity activity,
                                      @NonNull RegistrationCodeRequest.Mode mode,
                                      @Nullable String captchaToken,
                                      @NonNull RegistrationCodeRequest.SmsVerificationCodeCallback callback)
  {
    RegistrationCodeRequest.requestSmsVerificationCode(activity, credentials, captchaToken, mode, callback);
  }

  /**
   * See {@link CodeVerificationRequest}.
   */
  public void verifyAccount(@NonNull Activity activity,
                            @Nullable String fcmToken,
                            @NonNull String code,
                            @Nullable String pin,
                            @Nullable PinRestoreRepository.TokenData tokenData,
                            @NonNull CodeVerificationRequest.VerifyCallback callback)
  {
    CodeVerificationRequest.verifyAccount(activity, credentials, fcmToken, credentials.getE164number(),code, pin, tokenData, callback);
  }

...

Credentials是根据手机号e164number和password创建的:

//Credentials.java

public final class Credentials {

  private final String e164number;
  private final String password;

  public Credentials(@NonNull String e164number, @NonNull String password) {
    this.e164number = e164number;
    this.password   = password;
  }

  public @NonNull String getE164number() {
    return e164number;
  }

  public @NonNull String getPassword() {
    return password;
  }
}

password来自RegistrationViewModel的secret:

//EnterCodeFragment.java

...

    RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());
...
//RegistrationViewModel.java

  private final String                                       secret;

  ...

  public RegistrationViewModel(@NonNull SavedStateHandle savedStateHandle) {
    secret = loadValue(savedStateHandle, "REGISTRATION_SECRET", Util.getSecret(18));

    number                        = savedStateHandle.getLiveData("NUMBER", NumberViewState.INITIAL);
    textCodeEntered               = savedStateHandle.getLiveData("TEXT_CODE_ENTERED", "");
    captchaToken                  = savedStateHandle.getLiveData("CAPTCHA");
    fcmToken                      = savedStateHandle.getLiveData("FCM_TOKEN");
    restoreFlowShown              = savedStateHandle.getLiveData("RESTORE_FLOW_SHOWN", false);
    successfulCodeRequestAttempts = savedStateHandle.getLiveData("SUCCESSFUL_CODE_REQUEST_ATTEMPTS", 0);
    requestLimiter                = savedStateHandle.getLiveData("REQUEST_RATE_LIMITER", new LocalCodeRequestRateLimiter(60_000));
    kbsTokenData                  = savedStateHandle.getLiveData("KBS_TOKEN");
    lockedTimeRemaining           = savedStateHandle.getLiveData("TIME_REMAINING", 0L);
    canCallAtTime                 = savedStateHandle.getLiveData("CAN_CALL_AT_TIME", 0L);
  }

secret 是根据Util.getSecret(18)生成的:

//Util.java

...

  public static String getSecret(int size) {
    byte[] secret = getSecretBytes(size);
    return Base64.encodeBytes(secret);
  }

  public static byte[] getSecretBytes(int size) {
    return getSecretBytes(new SecureRandom(), size);
  }
...

即根据随机数生成的一个secret。
即客户端注册时带上的Credentials中的password是客户端根据随机数生成的。

手机号输入页面

在这里插入图片描述
手机号注册页面 EnterPhoneNumberFragment

点击注册按钮:

//EnterPhoneNumberFragment.java

  private void handleRegister(@NonNull Context context) {
    if (TextUtils.isEmpty(countryCode.getText())) {
      Toast.makeText(context, getString(R.string.RegistrationActivity_you_must_specify_your_country_code), Toast.LENGTH_LONG).show();
      return;
    }

    if (TextUtils.isEmpty(this.number.getText())) {
      Toast.makeText(context, getString(R.string.RegistrationActivity_you_must_specify_your_phone_number), Toast.LENGTH_LONG).show();
      return;
    }

    final NumberViewState number     = getModel().getNumber();
    final String          e164number = number.getE164Number();

    if (!number.isValid()) {
      Dialogs.showAlertDialog(context,
        getString(R.string.RegistrationActivity_invalid_number),
        String.format(getString(R.string.RegistrationActivity_the_number_you_specified_s_is_invalid), e164number));
      return;
    }

    PlayServicesUtil.PlayServicesStatus fcmStatus = PlayServicesUtil.getPlayServicesStatus(context);

    if (fcmStatus == PlayServicesUtil.PlayServicesStatus.SUCCESS) {
      handleRequestVerification(context, e164number, true);
    } else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.MISSING) {
      handlePromptForNoPlayServices(context, e164number);
    } else if (fcmStatus == PlayServicesUtil.PlayServicesStatus.NEEDS_UPDATE) {
      GoogleApiAvailability.getInstance().getErrorDialog(requireActivity(), ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED, 0).show();
    } else {
      Dialogs.showAlertDialog(context, getString(R.string.RegistrationActivity_play_services_error),
        getString(R.string.RegistrationActivity_google_play_services_is_updating_or_unavailable));
    }
  }

EnterPhoneNumberFragment#requestVerificationCode:

  private void requestVerificationCode(String e164number, @NonNull RegistrationCodeRequest.Mode mode) {
    RegistrationViewModel model   = getModel();
    String                captcha = model.getCaptchaToken();
    model.clearCaptchaResponse();

    NavController navController = Navigation.findNavController(register);

    if (!model.getRequestLimiter().canRequest(mode, e164number, System.currentTimeMillis())) {
      Log.i(TAG, "Local rate limited");
      navController.navigate(EnterPhoneNumberFragmentDirections.actionEnterVerificationCode());
      cancelSpinning(register);
      enableAllEntries();
      return;
    }

    RegistrationService registrationService = RegistrationService.getInstance(e164number, model.getRegistrationSecret());

    registrationService.requestVerificationCode(requireActivity(), mode, captcha,
      new RegistrationCodeRequest.SmsVerificationCodeCallback() {

        @Override
        public void onNeedCaptcha() {
          if (getContext() == null) {
            Log.i(TAG, "Got onNeedCaptcha response, but fragment is no longer attached.");
            return;
          }
          navController.navigate(EnterPhoneNumberFragmentDirections.actionRequestCaptcha());
          cancelSpinning(register);
          enableAllEntries();
          model.getRequestLimiter().onUnsuccessfulRequest();
          model.updateLimiter();
        }

...

RegistrationService#requestVerificationCode
RegistrationCodeRequest.requestSmsVerificationCode

点击NEXT按钮,调用的是PushServiceSocket#requestSmsVerificationCode
请求的接口是 /v1/accounts/sms/code/%s?client=%s

验证码输入页面

在这里插入图片描述
输入验证码页面 EnterCodeFragment

验证码输入未完成,调用的是PushServiceSocket#verifyAccountCode,请求的接口是:/v1/accounts/code/%s

验证码输入完成:

  private void setOnCodeFullyEnteredListener(VerificationCodeView verificationCodeView) {
    verificationCodeView.setOnCompleteListener(code -> {
      RegistrationViewModel model = getModel();

      model.onVerificationCodeEntered(code);
      callMeCountDown.setVisibility(View.INVISIBLE);
      wrongNumber.setVisibility(View.INVISIBLE);
      keyboard.displayProgress();

      RegistrationService registrationService = RegistrationService.getInstance(model.getNumber().getE164Number(), model.getRegistrationSecret());

      registrationService.verifyAccount(requireActivity(), model.getFcmToken(), code, null, null, null,
        new CodeVerificationRequest.VerifyCallback() {

          @Override
          public void onSuccessfulRegistration() {
            keyboard.displaySuccess().addListener(new AssertedSuccessListener<Boolean>() {
              @Override
              public void onSuccess(Boolean result) {
                handleSuccessfulRegistration();
              }
            });
          }

...

执行registrationService.verifyAccount,验证成功后执行handleSuccessfulRegistration():

private void handleSuccessfulRegistration() {
    Navigation.findNavController(requireView()).navigate(EnterCodeFragmentDirections.actionSuccessfulRegistration());
}

即注册成功后进入会一个闪屏页面:RegistrationCompleteFragment,然后会处理一些逻辑,并进入MainActivity:

  @Override
  public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    FragmentActivity activity = requireActivity();

/*
    //设置pin码的功能先不要
    if (SignalStore.storageServiceValues().needsAccountRestore()) {
      activity.startActivity(new Intent(activity, PinRestoreActivity.class));
    } else if (!isReregister()) {
      final Intent main    = new Intent(activity, MainActivity.class);
      final Intent profile = EditProfileActivity.getIntentForUserProfile(activity);

      Intent kbs = CreateKbsPinActivity.getIntentForPinCreate(requireContext());
      activity.startActivity(chainIntents(chainIntents(profile, kbs), main));
    }
*/

/*    final Intent main    = new Intent(activity, MainActivity.class);
    activity.startActivity(main);*/

    final Intent main    = new Intent(activity, WalletRegistreAndImportActivity.class);
    activity.startActivity(main);


    activity.finish();
    ActivityNavigator.applyPopAnimationsToPendingTransition(activity);
  }

登录

项目中建立websocket连接前,必须先发送https请求登录成功,登录成功后会发送https请求获取证书,然后携带着证书去向服务器建立websocket连接,如果证书无效或者已经过期,则建立websocket连接时的握手过程会失败,服务器会返回403状态码的消息,正常应该会返回101的状态码。

会话列表页面

在这里插入图片描述

ConversationListFragment ,布局文件是:conversation_list_fragment.xml
顶部的搜索栏是DarkOverflowToolbar

ConversationListFragment 属于 MainActivity,点击会话列表的item,执行的是onConversationClick(注意不是onConversationClicked):

//ConversationListFragment.java

  @Override
  public void onConversationClick(Conversation conversation) {
    if (actionMode == null) {
      handleCreateConversation(conversation.getThreadRecord().getThreadId(), conversation.getThreadRecord().getRecipient(), conversation.getThreadRecord().getDistributionType());
    } else {
      defaultAdapter.toggleConversationInBatchSet(conversation);

      if (defaultAdapter.getBatchSelectionIds().size() == 0) {
        actionMode.finish();
      } else {
        actionMode.setTitle(String.valueOf(defaultAdapter.getBatchSelectionIds().size()));
        setCorrectMenuVisibility(actionMode.getMenu());
      }
    }
  }

进入的是ConversationFragment页面,ConversationFragment属于ConversationActivity。

  private void handleCreateConversation(long threadId, Recipient recipient, int distributionType) {
    getNavigator().goToConversation(recipient.getId(), threadId, distributionType, -1);
  }
  public void goToConversation(@NonNull RecipientId recipientId, long threadId, int distributionType, int startingPosition) {
    Intent intent = ConversationActivity.buildIntent(activity, recipientId, threadId, distributionType, startingPosition);

    activity.startActivity(intent);
    activity.overridePendingTransition(R.anim.slide_from_end, R.anim.fade_scale_out);
  }

顶部的actionbar

搜索栏的左侧图标是AppCompatImageButton

右侧的选项菜单是ActionMenuView

在这里插入图片描述

这里有两个toolbar,默认是toolbar:DarkOverflowToolbar
点击搜索按钮显示的页面时是search_toolbar:SearchToolbar

点击首页的左上角进入的页面

在这里插入图片描述

ApplicationPreferencesActivity

点击首页顶部的搜索按钮进入的页面

还是在会话列表页ConversationListFragment.java,只是数据是过滤后的。
搜索按钮是searchToolbar,

//ConversationListFragment.java

  private void initializeSearchListener() {
    searchAction.setOnClickListener(v -> {
      searchToolbar.display(searchAction.getX() + (searchAction.getWidth() / 2.0f),
                            searchAction.getY() + (searchAction.getHeight() / 2.0f));
    });

    searchToolbar.setListener(new SearchToolbar.SearchListener() {
      @Override
      public void onSearchTextChange(String text) {
        String trimmed = text.trim();

        viewModel.updateQuery(trimmed);

        if (trimmed.length() > 0) {
          if (activeAdapter != searchAdapter) {
            setAdapter(searchAdapter);
            list.removeItemDecoration(searchAdapterDecoration);
            list.addItemDecoration(searchAdapterDecoration);
          }
        } else {
          if (activeAdapter != defaultAdapter) {
            list.removeItemDecoration(searchAdapterDecoration);
            setAdapter(defaultAdapter);
          }
        }
      }

      @Override
      public void onSearchClosed() {
        list.removeItemDecoration(searchAdapterDecoration);
        setAdapter(defaultAdapter);
      }
    });
  }

点击首页右下角的写短信浮动按钮进入的页面

进入的是联系人页面
在这里插入图片描述
这个按钮的点击事件:

//ConversationListFragment.java

    fab.setOnClickListener(v -> startActivity(new Intent(getActivity(), NewConversationActivity.class)));
    cameraFab.setOnClickListener(v -> {
      Permissions.with(requireActivity())
                 .request(Manifest.permission.CAMERA)
                 .ifNecessary()
                 .withRationaleDialog(getString(R.string.ConversationActivity_to_capture_photos_and_video_allow_signal_access_to_the_camera), R.drawable.ic_camera_solid_24)
                 .withPermanentDenialDialog(getString(R.string.ConversationActivity_signal_needs_the_camera_permission_to_take_photos_or_video))
                 .onAllGranted(() -> startActivity(MediaSendActivity.buildCameraFirstIntent(requireActivity())))
                 .onAnyDenied(() -> Toast.makeText(requireContext(), R.string.ConversationActivity_signal_needs_camera_permissions_to_take_photos_or_video, Toast.LENGTH_LONG).show())
                 .execute();
    });

启动了一个新的Activity:NewConversationActivity:

/**
 * Activity container for starting a new conversation.
 *
 * @author Moxie Marlinspike
 *
 */
public class NewConversationActivity extends ContactSelectionActivity
                                    implements ContactSelectionListFragment.ListCallback
{
	...
}

NewConversationActivity ,继承自ContactSelectionActivity ,页面是ContactSelectionListFragment,布局文件:contact_selection_activity.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_gravity="center"
              android:layout_height="fill_parent"
              android:layout_width="fill_parent"
              android:orientation="vertical"
              xmlns:android="http://schemas.android.com/apk/res/android"
              xmlns:app="http://schemas.android.com/apk/res-auto">

    <org.thoughtcrime.securesms.components.ContactFilterToolbar
            android:id="@+id/toolbar"
            android:layout_height="?attr/actionBarSize"
            android:layout_width="match_parent"
            android:minHeight="?attr/actionBarSize"
            android:elevation="4dp"
            android:theme="?attr/actionBarStyle"
            app:navigationIcon="@drawable/ic_arrow_left_24"
            app:contentInsetStartWithNavigation="0dp"/>

    <fragment android:id="@+id/contact_selection_list_fragment"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:name="org.thoughtcrime.securesms.ContactSelectionListFragment" />

</LinearLayout>

该页面的顶部的搜索栏是ContactFilterToolbar,看下顶部的搜索栏的布局:
在这里插入图片描述

邀请好友页面

在这里插入图片描述

InviteActivity
invite_activity.xml

顶部是Toolbar

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值