[Chat][Feature] add chat library structure (#404)

This commit is contained in:
Inderpal Singh Aulakh 2022-08-31 10:23:30 -07:00 коммит произвёл GitHub
Родитель e51c43ec9f
Коммит ae6fdc63af
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
26 изменённых файлов: 625 добавлений и 2 удалений

1
azure-communication-ui/chat/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
/build

Просмотреть файл

@ -0,0 +1,43 @@
plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk 31
resourcePrefix 'azure_communication_ui_call_'
defaultConfig {
minSdk 21
targetSdk 31
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "androidx.core:core-ktx:$androidx_core_ktx_version"
implementation "androidx.appcompat:appcompat:$androidx_appcompat_version"
implementation "androidx.fragment:fragment-ktx:$androidx_fragment_ktx_version"
implementation "androidx.navigation:navigation-fragment-ktx:$androidx_navigation_fragment_ktx_version"
testImplementation "junit:junit:$junit_version"
androidTestImplementation "androidx.test.ext:junit:$androidx_junit_version"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidx_espresso_core_version"
}

Просмотреть файл

21
azure-communication-ui/chat/proguard-rules.pro поставляемый Normal file
Просмотреть файл

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
}
}

Просмотреть файл

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.azure.android.communication.ui.chat"
>
</manifest>

Просмотреть файл

@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat;
public class ChatComposite {
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat;
import androidx.annotation.NonNull;
/**
* Defines the base type of custom Exception that can be thrown by this Library.
*/
public final class ChatCompositeException extends RuntimeException {
/**
* Constructs a new Chat Composite exception with the specified error message and cause. Note
* that the error message associated with "cause" is not automatically incorporated into this
* exception's error message.
*
* @param errorMessage - the error message. The error message can be retrieved by the
* getMessage() method
* @param cause - the cause (which is saved for later retrieval by the getCause() method). A
* null value is permitted, and indicates that the cause is non-existent or unknown.
*/
public ChatCompositeException(final String errorMessage, @NonNull final Throwable cause) {
super(errorMessage, cause);
}
/**
* Constructs a new Chat Composite exception with the specified cause and message of
* (cause==null ? null : cause.toString()) (which typically contains the class and detail message
* of cause). This constructor is useful for exceptions that are little more than wrappers for
* other throwables.
*
* @param cause - the cause (which is saved for later retrieval by the Throwable.getCause() method).
* A null value is permitted, and indicates that the cause is nonexistent or unknown.
*/
public ChatCompositeException(final Throwable cause) {
super(cause);
}
}

Просмотреть файл

@ -0,0 +1,114 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.locator
import java.util.HashMap
/* Heterogeneous Service Locator
Will lazily construct objects as the graph requires.
Register TypedBuilders for each class you want
Use locate<T>(Class<T> clazz) to get/build the instance
Usage:
addTypedBuilder {
BasicObjectHello()
}
addTypedBuilder {
BasicObjectWorld()
}
addTypedBuilder {
BasicObjectHelloWorld(
locate(),
locate()
)
}
val basicObjectHello = locate<BasicObjectHelloWorld>()
*/
internal typealias TypedBuilder<T> = () -> T
internal class ServiceLocator {
internal interface Disposable {
fun dispose()
}
// @PublishedApi on internal will expose the value, but obfuscate it.
//
// This has the effect of "hiding" api that must be public
// The reason for this "reified" type parameters. Reify is to "make more real/concrete"
//
// Reify only works on inline functions. inline functions can't use private member variable
// because they are "inlined" to the calling code and can't see private scope.
//
// We Reify our types, because it lets us have much nicer syntax.
// Instead of having to pass Class objects as parameters, we can get Class from <T>
// This lets us register typed builders and locate them simple.
//
// E.g.
// ```
// addTypedBuilder() { MyClassImpl() as MyClass }
// locate<MyClass>()
// ```
@PublishedApi
internal val builders = HashMap<Any, TypedBuilder<*>>()
@PublishedApi
internal val implementations = HashMap<Any, Any>()
// Adds a typed builder
// Note: For core services, use Constructor Injection
// This will allow us to "initializeAll" and validate the tree
inline fun <reified T> addTypedBuilder(noinline builder: TypedBuilder<T>) {
builders[T::class.java] = builder
}
// This resets the Service Locator to Initial
// All Implementations are cleared
// All Builders are removed
fun clear() {
for (implementation in implementations.values) {
if (implementation is Disposable) {
implementation.dispose()
}
}
builders.clear()
implementations.clear()
}
// Locate a class
// locate<MyClass()
//
// Will initialize the class if it doesn't exist
inline fun <reified T> locate(): T {
require(builders.containsKey(T::class.java)) { "Builder for ${T::class.java} Does not exist" }
return if (implementations.containsKey(T::class.java)) {
implementations[T::class.java] as T
} else {
val instance = builders[T::class.java]!!()!!
implementations[T::class.java] = instance
instance as T
}
}
// Initialize the entire tree
// This will allow us to crash-fast and cache-fast
inline fun initializeAll() {
builders.keys.filter { !implementations.containsKey(it) }.forEach {
implementations[it] = builders[it]?.invoke() as Any
}
}
companion object {
private val locatorMap = HashMap<Int, ServiceLocator>()
fun getInstance(instanceId: Int): ServiceLocator {
if (locatorMap.containsKey(instanceId)) {
return locatorMap[instanceId]!!
}
locatorMap[instanceId] = ServiceLocator()
return locatorMap[instanceId]!!
}
}
}

Просмотреть файл

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.logger
import android.util.Log
internal class DefaultLogger : Logger {
private val tag = "communication.ui"
override fun info(message: String) {
Log.i(tag, message)
}
override fun debug(message: String) {
Log.d(tag, message)
}
override fun warning(message: String) {
Log.w(tag, message)
}
override fun error(message: String, error: Throwable?) {
Log.e(tag, message, error)
}
}

Просмотреть файл

@ -0,0 +1,11 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.logger
internal interface Logger {
fun info(message: String)
fun debug(message: String)
fun warning(message: String)
fun error(message: String, error: Throwable?)
}

Просмотреть файл

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.models;
import com.azure.android.communication.ui.chat.ChatComposite;
/**
* ChatCompositeLocalOptions for ChatComposite.launch.
*
* <p>
* Local Options for the Chat Composite. These options are not shared with the server and impact local views only.
* E.g. The Local Participant Name if it differs from the display name you'd like to share with the server.
* </p>
* <pre>
*
* &#47;&#47; Initialize the chat composite builder
* final ChatCompositeBuilder builder = new ChatCompositeBuilder&#40;&#41;;
*
* &#47;&#47; Build the chat composite
* ChatComposite chatComposite = builder.build&#40;&#41;;
*
* &#47;&#47; Build the ChatCompositeLocalOptions with {@link ChatCompositeParticipantViewData}
* ChatCompositeLocalOptions localOptions = new ChatCompositeLocalOptions(
* new ChatCompositeParticipantViewData&#40;...&#41);
*
* &#47;&#47; Launch chat
* chatComposite.launch&#40;.., .., localOptions&#41
* </pre>
*
* @see ChatComposite
*/
public final class ChatCompositeLocalOptions {
private final ChatCompositeParticipantViewData participantViewData;
/**
* Create Local Options.
*
* @param participantViewData The {@link ChatCompositeParticipantViewData};
* @see ChatCompositeParticipantViewData
*/
public ChatCompositeLocalOptions(final ChatCompositeParticipantViewData participantViewData) {
this.participantViewData = participantViewData;
}
/**
* Get {@link ChatCompositeParticipantViewData}.
*
* @return The {@link ChatCompositeParticipantViewData};
*/
public ChatCompositeParticipantViewData getParticipantViewData() {
return participantViewData;
}
}

Просмотреть файл

@ -0,0 +1,100 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.models;
import android.graphics.Bitmap;
import android.widget.ImageView;
import com.azure.android.communication.ui.chat.ChatComposite;
/**
* ChatCompositeParticipantViewData for participant.
*
* <pre>
*
* &#47;&#47; Initialize the chat composite builder
* final ChatCompositeBuilder builder = new ChatCompositeBuilder&#40;&#41;;
*
* &#47;&#47; Build the chat composite
* ChatComposite chatComposite = builder.build&#40;&#41;;
*
* &#47;&#47; Build the ChatCompositeLocalOptions with {@link ChatCompositeParticipantViewData}
* ChatCompositeLocalOptions localOptions = new ChatCompositeLocalOptions(
* new ChatCompositeParticipantViewData&#40;...&#41);
*
* chatComposite.launch(..., ..., localOptions);
*
* </pre>
*
* @see ChatCompositeLocalOptions
*/
public final class ChatCompositeParticipantViewData {
private Bitmap avatarBitmap;
private String displayName;
private ImageView.ScaleType scaleType = ImageView.ScaleType.FIT_XY;
/**
* Set scaleType.
*
* Will not take affect if called after {@link ChatCompositeParticipantViewData} passed to {@link ChatComposite}
*
* @return The {@link ChatCompositeParticipantViewData};
*/
public ChatCompositeParticipantViewData setScaleType(final ImageView.ScaleType scaleType) {
this.scaleType = scaleType;
return this;
}
/**
* Get scaleType.
*
* Will not take affect if called after {@link ChatCompositeParticipantViewData} passed to {@link ChatComposite}
*
* @return The {@link ImageView.ScaleType};
*/
public ImageView.ScaleType getScaleType() {
return scaleType;
}
/**
* Set display name.
*
* @return The {@link ChatCompositeParticipantViewData};
*/
public ChatCompositeParticipantViewData setDisplayName(final String displayName) {
this.displayName = displayName;
return this;
}
/**
* Get display name.
*
* @return The {@link String};
*/
public String getDisplayName() {
return displayName;
}
/**
* Get avatar Bitmap.
*
* @return The {@link Bitmap};
*/
public Bitmap getAvatarBitmap() {
return avatarBitmap;
}
/**
* Set avatar Bitmap.
*
* Will not take affect if called after {@link ChatCompositeParticipantViewData} passed to {@link ChatComposite}
*
* @return The {@link ChatCompositeParticipantViewData};
*/
public ChatCompositeParticipantViewData setAvatarBitmap(final Bitmap avatarBitmap) {
this.avatarBitmap = avatarBitmap;
return this;
}
}

Просмотреть файл

@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
/**
* Package containing the classes for chat-composite. Azure android communication chat composite.
*/
package com.azure.android.communication.ui.chat;

Просмотреть файл

@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.presentation
import androidx.appcompat.app.AppCompatActivity
internal class ChatCompositeActivity : AppCompatActivity()

Просмотреть файл

@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.redux
import android.os.Handler
import android.os.Looper
import com.azure.android.communication.ui.chat.ChatCompositeException
import com.azure.android.communication.ui.chat.redux.action.Action
import com.azure.android.communication.ui.chat.redux.reducer.Reducer
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlin.coroutines.CoroutineContext
internal class AppStore<S>(
initialState: S,
private val reducer: Reducer<S>,
middlewares: MutableList<Middleware<S>>,
dispatcher: CoroutineContext,
) : Store<S> {
// Any exceptions encountered in the reducer are rethrown to crash the app and not get silently ignored.
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
Handler(Looper.getMainLooper()).postAtFrontOfQueue {
throw ChatCompositeException("App store exception while reducing state", throwable)
}
// At this point (after an exception) we don't want to accept any more work.
scope.cancel()
}
private val dispatcherWithExceptionHandler = dispatcher + exceptionHandler
private val scope = CoroutineScope(dispatcher)
private val stateFlow = MutableStateFlow(initialState)
private var middlewareMap: List<(Dispatch) -> Dispatch> =
middlewares.map { m -> m.invoke(this) }
private var middlewareDispatch = compose(middlewareMap)(::reduce)
override fun end() {
scope.cancel()
middlewareMap = emptyList()
middlewareDispatch = compose(middlewareMap)(::reduce)
}
override fun dispatch(action: Action) {
scope.launch(dispatcherWithExceptionHandler) {
middlewareDispatch(action)
}
}
override fun getStateFlow(): MutableStateFlow<S> {
return stateFlow
}
override fun getCurrentState(): S {
return stateFlow.value
}
private fun reduce(action: Action) {
stateFlow.value = reducer.reduce(stateFlow.value, action)
}
private fun compose(functions: List<(Dispatch) -> Dispatch>): (Dispatch) -> Dispatch =
{ dispatch ->
functions.foldRight(
dispatch
) { nextDispatch, composed -> nextDispatch(composed) }
}
}

Просмотреть файл

@ -0,0 +1,9 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.redux
import com.azure.android.communication.ui.chat.redux.action.Action
internal typealias Dispatch = (Action) -> Unit
internal typealias Middleware<State> = (store: Store<State>) -> (next: Dispatch) -> Dispatch

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.redux
import com.azure.android.communication.ui.chat.redux.action.Action
import kotlinx.coroutines.flow.MutableStateFlow
internal interface Store<S> {
fun dispatch(action: Action)
fun getStateFlow(): MutableStateFlow<S>
fun getCurrentState(): S
fun end()
}

Просмотреть файл

@ -0,0 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.redux.action
internal interface Action

Просмотреть файл

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.redux.middleware
import com.azure.android.communication.ui.chat.redux.Dispatch
import com.azure.android.communication.ui.chat.redux.Middleware
import com.azure.android.communication.ui.chat.redux.Store
import com.azure.android.communication.ui.chat.redux.state.ReduxState
internal interface ChatMiddleware
internal class ChatMiddlewareImpl :
Middleware<ReduxState>,
ChatMiddleware {
override fun invoke(store: Store<ReduxState>): (next: Dispatch) -> Dispatch {
TODO("Not yet implemented")
}
}

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.redux.reducer
import com.azure.android.communication.ui.chat.redux.action.Action
internal interface Reducer<S> {
fun reduce(state: S, action: Action): S
}

Просмотреть файл

@ -0,0 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.redux.state
internal interface ReduxState

Просмотреть файл

@ -0,0 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.service
internal class ChatService

Просмотреть файл

@ -0,0 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.service.sdk
internal class ChatSDKWrapper

Просмотреть файл

@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat
import org.junit.Test
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
}
}

Просмотреть файл

@ -1,3 +1,3 @@
include ':calling'
include ':azure-communication-ui-demo-app'
include ':chat'
include ':azure-communication-ui-demo-app'