introduce SetupActiviy

This commit is contained in:
Daniel Gultsch 2023-02-15 21:04:54 +01:00
parent 35360fde91
commit eeac779e25
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
29 changed files with 860 additions and 11 deletions

View file

@ -1,6 +1,12 @@
// Top-level build file where you can add configuration options common to all
// sub-projects/modules.
buildscript {
ext {
room_version = "2.5.0"
navVersion = '2.5.3'
appcompatVersion = "1.6.1"
lifecycleVersion = "2.2.0"
}
repositories {
google()
mavenCentral()
@ -8,11 +14,13 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:7.4.1'
classpath "com.diffplug.spotless:spotless-plugin-gradle:6.13.0"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navVersion"
}
}
apply plugin: 'com.android.application'
apply plugin: "com.diffplug.spotless"
apply plugin: "androidx.navigation.safeargs"
repositories {
@ -45,17 +53,25 @@ dependencies {
// Conversations 3.0 dependencies
def room_version = "2.5.0"
implementation project(':libs:annotation')
annotationProcessor project(':libs:annotation-processor')
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8'
implementation "androidx.appcompat:appcompat:$rootProject.ext.appcompatVersion"
implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.ext.lifecycleVersion"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
implementation "androidx.room:room-guava:$room_version"
implementation "androidx.navigation:navigation-fragment:$rootProject.ext.navVersion"
implementation "androidx.navigation:navigation-ui:$rootProject.ext.navVersion"
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "androidx.security:security-crypto:1.0.0"
@ -82,8 +98,7 @@ dependencies {
quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.0.1'
implementation 'org.sufficientlysecure:openpgp-api:10.0'
implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
implementation 'androidx.appcompat:appcompat:1.6.0'
implementation 'androidx.exifinterface:exifinterface:1.3.5'
implementation 'androidx.exifinterface:exifinterface:1.3.6'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.google.android.material:material:1.8.0'

View file

@ -85,7 +85,7 @@
android:networkSecurityConfig="@xml/network_security_configuration"
android:preserveLegacyExternalStorage="true"
android:requestLegacyExternalStorage="true"
android:theme="@style/ConversationsTheme"
android:theme="@style/Conversations3Theme"
tools:replace="android:label"
tools:targetApi="q">
@ -121,6 +121,14 @@
</intent-filter>
</receiver>
<activity android:exported="true" android:name="im.conversations.android.ui.activity.SetupActivity"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.ShareLocationActivity"
android:label="@string/title_activity_share_location" />
@ -138,10 +146,7 @@
android:name=".ui.ConversationActivity"
android:exported="true"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.ConversationsActivity"

View file

@ -1,16 +1,22 @@
package im.conversations.android;
import android.app.Application;
import com.google.android.material.color.DynamicColors;
import im.conversations.android.xmpp.ConnectionPool;
import java.security.SecureRandom;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Conversations extends Application {
private static final Logger LOGGER = LoggerFactory.getLogger(Conversations.class);
public static final SecureRandom SECURE_RANDOM = new SecureRandom();
@Override
public void onCreate() {
super.onCreate();
ConnectionPool.getInstance(this).reconfigure();
DynamicColors.applyToActivitiesIfAvailable(this);
}
}

View file

@ -0,0 +1,39 @@
package im.conversations.android.ui;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.material.elevation.SurfaceColors;
public final class Activities {
private Activities() {}
public static void setStatusAndNavigationBarColors(
final AppCompatActivity activity, final View view) {
final var isLightMode = isLightMode(activity);
final var window = activity.getWindow();
final var flags = view.getSystemUiVisibility();
// an elevation of 4 matches the MaterialToolbar elevation
window.setStatusBarColor(SurfaceColors.SURFACE_0.getColor(activity));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
window.setNavigationBarColor(SurfaceColors.SURFACE_1.getColor(activity));
if (isLightMode) {
view.setSystemUiVisibility(
flags
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
| View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
}
} else if (isLightMode) {
view.setSystemUiVisibility(flags | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
}
private static boolean isLightMode(final Context context) {
final int nightModeFlags =
context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
return nightModeFlags != Configuration.UI_MODE_NIGHT_YES;
}
}

View file

@ -0,0 +1,31 @@
package im.conversations.android.ui;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
import androidx.databinding.BindingAdapter;
import androidx.lifecycle.LiveData;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import com.google.common.base.Supplier;
public class BindingAdapters {
@BindingAdapter("errorText")
public static void setErrorText(
final TextInputLayout textInputLayout, final LiveData<String> error) {
textInputLayout.setError(error.getValue());
}
@BindingAdapter("editorAction")
public static void setEditorAction(
final TextInputEditText editText, final @NonNull Supplier<Boolean> callback) {
editText.setOnEditorActionListener(
(v, actionId, event) -> {
// event is null when using software keyboard
if (event == null || event.getAction() == KeyEvent.ACTION_UP) {
return Boolean.TRUE.equals(callback.get());
}
return true;
});
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright 2019 Daniel Gultsch
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.conversations.android.ui;
import java.util.concurrent.atomic.AtomicBoolean;
public class Event<T> {
private final T event;
private final AtomicBoolean isConsumable = new AtomicBoolean(true);
public Event(T event) {
this.event = event;
}
public T consume() {
if (isConsumable.compareAndSet(true, false)) {
return event;
}
throw new IllegalStateException("Event has already been consumed");
}
public boolean isConsumable() {
return isConsumable.get();
}
}

View file

@ -0,0 +1,23 @@
package im.conversations.android.ui;
import androidx.annotation.IdRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
public final class NavControllers {
private NavControllers() {}
public static NavController findNavController(
final AppCompatActivity activity, @IdRes int fragmentId) {
final FragmentManager fragmentManager = activity.getSupportFragmentManager();
final Fragment fragment = fragmentManager.findFragmentById(fragmentId);
if (fragment instanceof NavHostFragment) {
return ((NavHostFragment) fragment).getNavController();
}
throw new IllegalStateException("Fragment was not of type NavHostFragment");
}
}

View file

@ -0,0 +1,50 @@
package im.conversations.android.ui.activity;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import eu.siacs.conversations.R;
import eu.siacs.conversations.SetupNavigationDirections;
import eu.siacs.conversations.databinding.ActivitySetupBinding;
import im.conversations.android.ui.Activities;
import im.conversations.android.ui.Event;
import im.conversations.android.ui.NavControllers;
import im.conversations.android.ui.model.SetupViewModel;
public class SetupActivity extends AppCompatActivity {
private SetupViewModel setupViewModel;
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActivitySetupBinding binding =
DataBindingUtil.setContentView(this, R.layout.activity_setup);
final ViewModelProvider viewModelProvider =
new ViewModelProvider(this, getDefaultViewModelProviderFactory());
this.setupViewModel = viewModelProvider.get(SetupViewModel.class);
this.setupViewModel.getRedirection().observe(this, this::onRedirectionEvent);
Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
}
private void onRedirectionEvent(final Event<SetupViewModel.Target> targetEvent) {
if (targetEvent.isConsumable()) {
final NavController navController = getNavController();
final SetupViewModel.Target target = targetEvent.consume();
switch (target) {
case ENTER_PASSWORD:
navController.navigate(SetupNavigationDirections.enterPassword());
break;
default:
throw new IllegalStateException(
String.format("Unable to navigate to target %s", target));
}
}
}
private NavController getNavController() {
return NavControllers.findNavController(this, R.id.nav_host_fragment);
}
}

View file

@ -0,0 +1,18 @@
package im.conversations.android.ui.fragment.setup;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import im.conversations.android.ui.model.SetupViewModel;
public abstract class AbstractSetupFragment extends Fragment {
SetupViewModel setupViewModel;
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ViewModelProvider viewModelProvider =
new ViewModelProvider(requireActivity(), getDefaultViewModelProviderFactory());
this.setupViewModel = viewModelProvider.get(SetupViewModel.class);
}
}

View file

@ -0,0 +1,3 @@
package im.conversations.android.ui.fragment.setup;
public class HostnameFragment extends AbstractSetupFragment {}

View file

@ -0,0 +1,24 @@
package im.conversations.android.ui.fragment.setup;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.FragmentPasswordBinding;
public class PasswordFragment extends AbstractSetupFragment {
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
FragmentPasswordBinding binding =
DataBindingUtil.inflate(inflater, R.layout.fragment_password, container, false);
binding.setSetupViewModel(setupViewModel);
binding.setLifecycleOwner(getViewLifecycleOwner());
return binding.getRoot();
}
}

View file

@ -0,0 +1,24 @@
package im.conversations.android.ui.fragment.setup;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.FragmentSignInBinding;
public class SignInFragment extends AbstractSetupFragment {
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
FragmentSignInBinding binding =
DataBindingUtil.inflate(inflater, R.layout.fragment_sign_in, container, false);
binding.setSetupViewModel(setupViewModel);
binding.setLifecycleOwner(getViewLifecycleOwner());
return binding.getRoot();
}
}

View file

@ -0,0 +1,63 @@
package im.conversations.android.ui.model;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import im.conversations.android.ui.Event;
import org.jetbrains.annotations.NotNull;
public class SetupViewModel extends AndroidViewModel {
private final MutableLiveData<String> xmppAddress = new MutableLiveData<>();
private final MutableLiveData<String> xmppAddressError = new MutableLiveData<>();
private final MutableLiveData<String> password = new MutableLiveData<>();
private final MutableLiveData<String> passwordError = new MutableLiveData<>();
private final MutableLiveData<Boolean> loading = new MutableLiveData<>(false);
private final MutableLiveData<Event<Target>> redirection = new MutableLiveData<>();
public SetupViewModel(@NonNull @NotNull Application application) {
super(application);
}
public LiveData<Boolean> isLoading() {
return this.loading;
}
public LiveData<String> getXmppAddressError() {
return Transformations.distinctUntilChanged(xmppAddressError);
}
public MutableLiveData<String> getXmppAddress() {
return this.xmppAddress;
}
public MutableLiveData<String> getPassword() {
return password;
}
public LiveData<String> getPasswordError() {
return Transformations.distinctUntilChanged(this.passwordError);
}
public boolean submitXmppAddress() {
redirection.postValue(new Event<>(Target.ENTER_PASSWORD));
return true;
}
public boolean submitPassword() {
return true;
}
public LiveData<Event<Target>> getRedirection() {
return this.redirection;
}
public enum Target {
ENTER_PASSWORD,
ENTER_HOSTNAME
}
}

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019 Daniel Gultsch
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@integer/slide_animation_duration"
android:fromXDelta="-100%p"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toXDelta="0" />
</set>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019 Daniel Gultsch
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@integer/slide_animation_duration"
android:fromXDelta="100%p"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toXDelta="0" />
</set>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019 Daniel Gultsch
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@integer/slide_animation_duration"
android:fromXDelta="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toXDelta="-100%p" />
</set>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019 Daniel Gultsch
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="@integer/slide_animation_duration"
android:fromXDelta="0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toXDelta="100%p" />
</set>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM12,6c1.93,0 3.5,1.57 3.5,3.5S13.93,13 12,13s-3.5,-1.57 -3.5,-3.5S10.07,6 12,6zM12,20c-2.03,0 -4.43,-0.82 -6.14,-2.88C7.55,15.8 9.68,15 12,15s4.45,0.8 6.14,2.12C16.43,19.18 14.03,20 12,20z" />
</vector>

View file

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z" />
</vector>

View file

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019 Daniel Gultsch
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/setup_navigation" />
</FrameLayout>
</layout>

View file

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019-2023 Daniel Gultsch
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<layout 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">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="false"
app:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/material_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/app_name"
app:menu="@menu/setup"
app:titleTextColor="?colorPrimary"
app:titleCentered="true" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="@{setupViewModel.isLoading() ? View.VISIBLE : View.INVISIBLE}" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:minHeight="@dimen/setup_main_min_height"
android:orientation="vertical"
android:padding="@dimen/setup_screen_padding">
<TextView
android:id="@+id/header"
android:textAppearance="?textAppearanceTitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/setup_screen_top_margin"
android:text="@string/welcome" />
<TextView
android:id="@+id/xmpp_address"
android:textAppearance="?textAppearanceBodyMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/header"
android:layout_marginTop="12sp"
android:layout_centerHorizontal="true"
android:text="@{setupViewModel.xmppAddress}"
tools:text="username@exmample.com"
app:drawableStartCompat="@drawable/ic_account_circle_24dp"
android:drawablePadding="8sp" />
<com.google.android.material.textfield.TextInputLayout
android:layout_marginTop="24sp"
android:id="@+id/password_input_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="488dp"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/xmpp_address"
android:layout_gravity="center_horizontal"
android:enabled="@{!setupViewModel.isLoading()}"
android:hint="@string/password"
app:errorText="@{setupViewModel.passwordError}">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="flagNoExtractUi|actionNext"
android:inputType="textPassword"
android:maxLines="1"
android:text="@={setupViewModel.password}"
app:editorAction="@{()->setupViewModel.submitPassword()}" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:enabled="@{!setupViewModel.isLoading()}"
android:onClick="@{(v) -> setupViewModel.submitPassword()}"
android:text="@string/next" />
</RelativeLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<data>
<import type="android.view.View" />
<variable
name="setupViewModel"
type="im.conversations.android.ui.model.SetupViewModel" />
</data>
</layout>

View file

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019-2023 Daniel Gultsch
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="false"
app:elevation="0dp">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/material_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/app_name"
app:menu="@menu/setup"
app:titleTextColor="?colorPrimary"
app:titleCentered="true" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="@{setupViewModel.isLoading() ? View.VISIBLE : View.INVISIBLE}" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:minHeight="@dimen/setup_main_min_height"
android:orientation="vertical"
android:padding="@dimen/setup_screen_padding">
<TextView
android:id="@+id/header"
android:textAppearance="?textAppearanceTitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="@dimen/setup_screen_top_margin"
android:text="@string/sign_in" />
<TextView
android:id="@+id/instructions"
android:textAppearance="?textAppearanceBodyMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/header"
android:layout_marginTop="12sp"
android:layout_centerHorizontal="true"
android:text="@string/sign_in_instruction" />
<com.google.android.material.textfield.TextInputLayout
android:layout_marginTop="24sp"
android:id="@+id/xmpp_address_input_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="488dp"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@+id/instructions"
android:layout_gravity="center_horizontal"
android:enabled="@{!setupViewModel.isLoading()}"
android:hint="@string/xmpp_address"
app:errorText="@{setupViewModel.xmppAddressError}">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/xmpp_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:imeOptions="flagNoExtractUi|actionNext"
android:inputType="textEmailAddress|textNoSuggestions"
android:maxLines="1"
android:text="@={setupViewModel.xmppAddress}"
app:editorAction="@{()->setupViewModel.submitXmppAddress()}" />
</com.google.android.material.textfield.TextInputLayout>
<Button
style="@style/Widget.Material3.Button.TextButton"
android:id="@+id/register"
android:layout_marginTop="12sp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_account_register"
android:layout_centerHorizontal="true"
android:textColor="?colorSecondary"
android:enabled="@{!setupViewModel.isLoading()}"
android:layout_below="@+id/xmpp_address_input_layout"
/>
<com.google.android.material.button.MaterialButton
android:id="@+id/next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:enabled="@{!setupViewModel.isLoading()}"
android:onClick="@{(v) -> setupViewModel.submitXmppAddress()}"
android:text="@string/next" />
</RelativeLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<data>
<import type="android.view.View" />
<variable
name="setupViewModel"
type="im.conversations.android.ui.model.SetupViewModel" />
</data>
</layout>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/account_settings"
android:icon="@drawable/ic_settings_24dp"
android:title="@string/account_settings"
app:showAsAction="ifRoom" />
</menu>

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright 2019 Daniel Gultsch
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<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/setup_navigation"
app:startDestination="@+id/signIn">
<action
android:id="@+id/enterPassword"
app:destination="@+id/password"
app:enterAnim="@anim/slide_from_right"
app:exitAnim="@anim/slide_to_left"
app:popEnterAnim="@anim/slide_from_left"
app:popExitAnim="@anim/slide_to_right" />
<fragment
android:id="@+id/signIn"
android:name="im.conversations.android.ui.fragment.setup.SignInFragment"
tools:layout="@layout/fragment_sign_in" />
<fragment
android:id="@+id/password"
android:name="im.conversations.android.ui.fragment.setup.PasswordFragment"
tools:layout="@layout/fragment_password" />
</navigation>

View file

@ -2,4 +2,7 @@
<dimen name="local_video_preview_height">96dp</dimen>
<dimen name="local_video_preview_width">128dp</dimen>
<dimen name="rtp_session_duration_top_margin">16dp</dimen>
<dimen name="setup_screen_top_margin">8dp</dimen>
<dimen name="setup_screen_padding">16dp</dimen>
<dimen name="setup_main_min_height">288dp</dimen>
</resources>

View file

@ -44,4 +44,7 @@
<dimen name="local_video_preview_height">128dp</dimen>
<dimen name="local_video_preview_width">96dp</dimen>
<dimen name="rtp_session_duration_top_margin">24dp</dimen>
<dimen name="setup_screen_top_margin">48dp</dimen>
<dimen name="setup_main_min_height">336dp</dimen>
<dimen name="setup_screen_padding">24dp</dimen>
</resources>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright 2019 Daniel Gultsch
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<resources>
<integer name="slide_animation_duration">300</integer>
</resources>

View file

@ -1005,5 +1005,11 @@
<string name="pref_up_push_server_title">Push Server</string>
<string name="pref_up_push_server_summary">A user-chosen push server to relay push messages via XMPP to your device.</string>
<string name="no_account_deactivated">None (deactivated)</string>
<string name="sign_in">Sign in</string>
<string name="sign_in_instruction">with your XMPP account.</string>
<string name="enter_your_password">Type in your password</string>
<string name="welcome">Welcome</string>
<string name="no_account_register">No account? Register</string>
<string name="account_settings">Account settings</string>
</resources>

View file

@ -1,6 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<style name="Conversations3Theme" parent="Theme.Material3.DayNight.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">?attr/isLightTheme</item>
</style>
<style name="ConversationsTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary">@color/green600</item>
<item name="colorPrimaryDark">@color/green800</item>