mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-06 04:19:38 +01:00
Merge branch 'master' into emoji2
Signed-off-by: Smarshall <99678760+Smarshal21@users.noreply.github.com>
This commit is contained in:
commit
561fd037c4
@ -38,4 +38,11 @@ indent_size=2
|
||||
|
||||
[*.{kt,kts}]
|
||||
# IDE does not follow this Ktlint rule strictly, but the default ordering is pretty good anyway, so let's ditch it
|
||||
ktlint_disabled_rules=import-ordering
|
||||
ktlint_code_style = android_studio
|
||||
insert_final_newline = true
|
||||
ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than=unset
|
||||
ktlint_function_signature_body_expression_wrapping=multiline
|
||||
ktlint_standard_import-ordering = disabled
|
||||
ktlint_standard_wrapping = enabled
|
||||
ij_kotlin_allow_trailing_comma = false
|
||||
ij_kotlin_allow_trailing_comma_on_call_site = false
|
||||
|
2
.github/workflows/analysis.yml
vendored
2
.github/workflows/analysis.yml
vendored
@ -38,7 +38,7 @@ jobs:
|
||||
repository: ${{ steps.get-vars.outputs.repo }}
|
||||
ref: ${{ steps.get-vars.outputs.branch }}
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 17
|
||||
|
2
.github/workflows/assembleFlavors.yml
vendored
2
.github/workflows/assembleFlavors.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 17
|
||||
|
2
.github/workflows/check.yml
vendored
2
.github/workflows/check.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 17
|
||||
|
6
.github/workflows/codeql.yml
vendored
6
.github/workflows/codeql.yml
vendored
@ -32,11 +32,11 @@ jobs:
|
||||
with:
|
||||
swap-size-gb: 10
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||
uses: github/codeql-action/init@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 17
|
||||
@ -46,4 +46,4 @@ jobs:
|
||||
echo "org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" > "$HOME/.gradle/gradle.properties"
|
||||
./gradlew assembleDebug
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||
uses: github/codeql-action/analyze@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
|
||||
|
2
.github/workflows/qa.yml
vendored
2
.github/workflows/qa.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
if: ${{ steps.check-secrets.outputs.ok == 'true' }}
|
||||
- name: set up JDK 17
|
||||
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
if: ${{ steps.check-secrets.outputs.ok == 'true' }}
|
||||
with:
|
||||
distribution: "temurin"
|
||||
|
2
.github/workflows/scorecard.yml
vendored
2
.github/workflows/scorecard.yml
vendored
@ -37,6 +37,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@74483a38d39275f33fcff5f35b679b5ca4a26a99 # v2.22.5
|
||||
uses: github/codeql-action/upload-sarif@b374143c1149a9115d881581d29b8390bbcbb59c # v3.22.11
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
2
.github/workflows/stale.yml
vendored
2
.github/workflows/stale.yml
vendored
@ -14,7 +14,7 @@ jobs:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0
|
||||
- uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0
|
||||
with:
|
||||
days-before-stale: 28
|
||||
days-before-close: 14
|
||||
|
4
.github/workflows/unit-tests.yml
vendored
4
.github/workflows/unit-tests.yml
vendored
@ -20,12 +20,12 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@0ab4596768b603586c0de567f2430c30f5b0d2b0 # v3.13.0
|
||||
uses: actions/setup-java@387ac29b308b003ca37ba93a6cab5eb57c8f5f93 # v4.0.0
|
||||
with:
|
||||
distribution: "temurin"
|
||||
java-version: 17
|
||||
- name: Run unit tests with coverage
|
||||
uses: gradle/gradle-build-action@842c587ad8aa4c68eeba24c396e15af4c2e9f30a # v2.9.0
|
||||
uses: gradle/gradle-build-action@87a9a15658c426a54dd469d4fc7dc1a73ca9d4a6 # v2.10.0
|
||||
with:
|
||||
arguments: testGplayDebugUnit
|
||||
- name: Upload test artifacts
|
||||
|
31
CHANGELOG.md
31
CHANGELOG.md
@ -5,6 +5,37 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
Types of changes can be: Added/Changed/Deprecated/Removed/Fixed/Security
|
||||
|
||||
## [18.0.0] - 2023-12-11
|
||||
|
||||
### Added
|
||||
- File captions
|
||||
- Note To Self
|
||||
- Recording consent
|
||||
- Share files by long press context menu (@Smarshal21)
|
||||
- Save files to storage (@FaribaKhandani)
|
||||
- Show active call in chat with accept call buttons
|
||||
|
||||
### Fixed
|
||||
- Not possible to delete voice, video, image, contact and location messages (@Smarshal21)
|
||||
- Hide "unread mention" bubble in search mode (@sowjanyakch)
|
||||
- Call notification screen remains open
|
||||
- Minor bug fixes (@parneet-guraya et al.)
|
||||
|
||||
Minimum: Android 7.0 Nougat
|
||||
|
||||
For a full list, please see https://github.com/nextcloud/talk-android/milestone/75?closed=1
|
||||
|
||||
## [17.1.3] - 2023-11-17
|
||||
|
||||
### Fixed
|
||||
- Login via Active Directory fails when using Umlauts in username
|
||||
- Crash when guest without name joins a call
|
||||
- Chat messages disappear on initial configuration change (e.g. screen rotation)
|
||||
|
||||
Minimum: Android 7.0 Nougat
|
||||
|
||||
For a full list, please see https://github.com/nextcloud/talk-android/milestone/78?closed=1
|
||||
|
||||
## [17.1.2] - 2023-10-19
|
||||
|
||||
### Fixed
|
||||
|
@ -289,7 +289,6 @@ We are using [Dagger 2](https://dagger.dev/) to inject dependencies into major A
|
||||
|
||||
* `Activity`
|
||||
* `Fragment`
|
||||
* `Controller`
|
||||
* `Service`
|
||||
* `BroadcastReceiver`
|
||||
* `ContentProvider`
|
||||
|
@ -7,7 +7,7 @@
|
||||
* @author Tim Krüger
|
||||
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
||||
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
* Copyright (C) 2021-2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* Copyright (C) 2022 Tim Krüger <t@timkrueger.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
@ -24,6 +24,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import com.github.spotbugs.snom.SpotBugsTask
|
||||
import com.github.spotbugs.snom.Confidence
|
||||
import com.github.spotbugs.snom.Effort
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
@ -46,8 +48,8 @@ android {
|
||||
|
||||
// mayor.minor.hotfix.increment (for increment: 01-50=Alpha / 51-89=RC / 90-99=stable)
|
||||
// xx .xxx .xx .xx
|
||||
versionCode 180000001
|
||||
versionName "18.0.0 Alpha 1"
|
||||
versionCode 180010004
|
||||
versionName "18.1.0 Alpha 04"
|
||||
|
||||
flavorDimensions "default"
|
||||
renderscriptTargetApi 19
|
||||
@ -127,6 +129,7 @@ android {
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
lint {
|
||||
@ -138,9 +141,9 @@ android {
|
||||
}
|
||||
|
||||
ext {
|
||||
androidxCameraVersion = "1.3.0"
|
||||
androidxCameraVersion = "1.3.1"
|
||||
coilKtVersion = "2.5.0"
|
||||
daggerVersion = "2.48.1"
|
||||
daggerVersion = "2.49"
|
||||
emojiVersion = "1.4.0"
|
||||
lifecycleVersion = '2.6.2'
|
||||
okhttpVersion = "4.12.0"
|
||||
@ -149,13 +152,13 @@ ext {
|
||||
parcelerVersion = "1.1.13"
|
||||
prismVersion = "2.0.0"
|
||||
retrofit2Version = "2.9.0"
|
||||
roomVersion = "2.6.0"
|
||||
workVersion = "2.8.1"
|
||||
roomVersion = "2.6.1"
|
||||
workVersion = "2.9.0"
|
||||
espressoVersion = "3.5.1"
|
||||
media3_version = "1.1.1"
|
||||
media3_version = "1.2.0"
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
configurations.configureEach {
|
||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||
@ -166,14 +169,14 @@ dependencies {
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'androidx.datastore:datastore-core:1.0.0'
|
||||
implementation 'androidx.datastore:datastore-preferences:1.0.0'
|
||||
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.3")
|
||||
detektPlugins("io.gitlab.arturbosch.detekt:detekt-formatting:1.23.4")
|
||||
|
||||
implementation fileTree(include: ['*'], dir: 'libs')
|
||||
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0"
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2"
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'com.google.android.material:material:1.10.0'
|
||||
implementation 'com.google.android.material:material:1.11.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation "androidx.emoji2:emoji2:${emojiVersion}"
|
||||
implementation "androidx.emoji2:emoji2-bundled:${emojiVersion}"
|
||||
@ -195,7 +198,7 @@ dependencies {
|
||||
implementation "androidx.camera:camera-camera2:${androidxCameraVersion}"
|
||||
implementation "androidx.camera:camera-lifecycle:${androidxCameraVersion}"
|
||||
implementation "androidx.camera:camera-view:${androidxCameraVersion}"
|
||||
implementation "androidx.exifinterface:exifinterface:1.3.6"
|
||||
implementation "androidx.exifinterface:exifinterface:1.3.7"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:${lifecycleVersion}"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${lifecycleVersion}"
|
||||
@ -210,8 +213,6 @@ dependencies {
|
||||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
|
||||
implementation "io.reactivex.rxjava2:rxjava:2.2.21"
|
||||
|
||||
implementation 'com.bluelinelabs:conductor:3.2.0'
|
||||
|
||||
implementation "com.squareup.okhttp3:okhttp:${okhttpVersion}"
|
||||
implementation "com.squareup.okhttp3:okhttp-urlconnection:${okhttpVersion}"
|
||||
implementation "com.squareup.okhttp3:logging-interceptor:${okhttpVersion}"
|
||||
@ -241,7 +242,7 @@ dependencies {
|
||||
implementation "org.parceler:parceler-api:$parcelerVersion"
|
||||
implementation 'eu.davidea:flexible-adapter:5.1.0'
|
||||
implementation 'eu.davidea:flexible-adapter-ui:1.0.0'
|
||||
implementation 'org.apache.commons:commons-lang3:3.13.0'
|
||||
implementation 'org.apache.commons:commons-lang3:3.14.0'
|
||||
implementation 'com.github.wooplr:Spotlight:1.3'
|
||||
implementation 'com.google.code.findbugs:jsr305:3.0.2'
|
||||
implementation 'com.github.nextcloud-deps:ChatKit:0.4.2'
|
||||
@ -286,8 +287,8 @@ dependencies {
|
||||
implementation 'androidx.core:core-ktx:1.12.0'
|
||||
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
testImplementation 'org.mockito:mockito-core:5.7.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:5.7.0'
|
||||
testImplementation 'org.mockito:mockito-core:5.8.0'
|
||||
androidTestImplementation 'org.mockito:mockito-android:5.8.0'
|
||||
testImplementation 'androidx.arch.core:core-testing:2.2.0'
|
||||
|
||||
androidTestImplementation "androidx.test:core:1.5.0"
|
||||
@ -302,19 +303,19 @@ dependencies {
|
||||
androidTestImplementation('com.android.support.test.espresso:espresso-intents:3.0.2')
|
||||
|
||||
spotbugsPlugins 'com.h3xstream.findsecbugs:findsecbugs-plugin:1.12.0'
|
||||
spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.0'
|
||||
spotbugsPlugins 'com.mebigfatguy.fb-contrib:fb-contrib:7.6.3'
|
||||
|
||||
gplayImplementation 'com.google.android.gms:play-services-base:18.2.0'
|
||||
gplayImplementation "com.google.firebase:firebase-messaging:23.3.1"
|
||||
gplayImplementation "com.google.firebase:firebase-messaging:23.4.0"
|
||||
|
||||
implementation 'androidx.activity:activity-ktx:1.8.0'
|
||||
implementation 'androidx.activity:activity-ktx:1.8.2'
|
||||
|
||||
implementation 'com.github.nextcloud.android-common:ui:0.12.0'
|
||||
implementation 'com.github.nextcloud.android-common:ui:0.13.0'
|
||||
|
||||
implementation 'com.github.nextcloud-deps:android-talk-webrtc:110.5481.0'
|
||||
}
|
||||
|
||||
task installGitHooks(type: Copy, group: "development") {
|
||||
tasks.register('installGitHooks', Copy) {
|
||||
description = "Install git hooks"
|
||||
from("../scripts/hooks") {
|
||||
include '*'
|
||||
@ -324,11 +325,11 @@ task installGitHooks(type: Copy, group: "development") {
|
||||
|
||||
spotbugs {
|
||||
ignoreFailures = true // should continue checking
|
||||
effort = "max"
|
||||
reportLevel = "medium"
|
||||
effort = Effort.MAX
|
||||
reportLevel = Confidence.valueOf('MEDIUM')
|
||||
}
|
||||
|
||||
tasks.withType(SpotBugsTask) { task ->
|
||||
tasks.withType(SpotBugsTask).configureEach { task ->
|
||||
String variantNameCap = task.name.replace("spotbugs", "")
|
||||
String variantName = variantNameCap.substring(0, 1).toLowerCase() + variantNameCap.substring(1)
|
||||
|
||||
@ -359,6 +360,6 @@ tasks.named("detekt").configure {
|
||||
}
|
||||
|
||||
detekt {
|
||||
config = files("../detekt.yml")
|
||||
input = files("src/")
|
||||
config.setFrom("../detekt.yml")
|
||||
source.setFrom("src/")
|
||||
}
|
||||
|
@ -32,6 +32,5 @@
|
||||
|
||||
<issue id="ObsoleteLintCustomCheck" severity="warning">
|
||||
<ignore path="**/jetified-annotation-experimental-1.**/**/lint.jar" />
|
||||
<ignore path="**/jetified-conductor-2.**/**/lint.jar" />
|
||||
</issue>
|
||||
</lint>
|
||||
|
@ -20,9 +20,12 @@ class ShareUtilsIT {
|
||||
return DateUtils.parseDate(
|
||||
dateStr, Locale.US,
|
||||
HttpUtils.httpDateFormatStr,
|
||||
"EEE, dd MMM yyyy HH:mm:ss zzz", // RFC 822, updated by RFC 1123 with any TZ
|
||||
"EEEE, dd-MMM-yy HH:mm:ss zzz", // RFC 850, obsoleted by RFC 1036 with any TZ.
|
||||
"EEE MMM d HH:mm:ss yyyy", // ANSI C's asctime() format
|
||||
// RFC 822, updated by RFC 1123 with any TZ
|
||||
"EEE, dd MMM yyyy HH:mm:ss zzz",
|
||||
// RFC 850, obsoleted by RFC 1036 with any TZ.
|
||||
"EEEE, dd-MMM-yy HH:mm:ss zzz",
|
||||
// ANSI C's asctime() format
|
||||
"EEE MMM d HH:mm:ss yyyy",
|
||||
// Alternative formats.
|
||||
"EEE, dd-MMM-yyyy HH:mm:ss z",
|
||||
"EEE, dd-MMM-yyyy HH-mm-ss z",
|
||||
@ -35,7 +38,7 @@ class ShareUtilsIT {
|
||||
"EEE,dd-MMM-yy HH:mm:ss z",
|
||||
"EEE,dd-MMM-yyyy HH:mm:ss z",
|
||||
"EEE, dd-MM-yyyy HH:mm:ss z",
|
||||
/* RI bug 6641315 claims a cookie of this format was once served by www.yahoo.com */
|
||||
// RI bug 6641315 claims a cookie of this format was once served by www.yahoo.com
|
||||
"EEE MMM d yyyy HH:mm:ss z"
|
||||
)
|
||||
}
|
||||
|
@ -38,9 +38,6 @@ public class ClosedInterfaceImpl implements ClosedInterface {
|
||||
|
||||
@Override
|
||||
public void setUpPushTokenRegistration() {
|
||||
// no push notifications for generic build flavour :(
|
||||
// If you want to develop push notifications without google play services, here is a good place to start...
|
||||
// Also have a look at app/src/gplay/AndroidManifest.xml to see how to include a service that handles push
|
||||
// notifications.
|
||||
// no push notifications for generic build variant
|
||||
}
|
||||
}
|
||||
|
@ -28,20 +28,24 @@ import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import autodagger.AutoInjector
|
||||
import com.google.android.gms.tasks.OnCompleteListener
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class GetFirebasePushTokenWorker(val context: Context, workerParameters: WorkerParameters) :
|
||||
Worker(context, workerParameters) {
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var appPreferences: AppPreferences? = null
|
||||
lateinit var appPreferences: AppPreferences
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
override fun doWork(): Result {
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
FirebaseMessaging.getInstance().token.addOnCompleteListener(
|
||||
OnCompleteListener { task ->
|
||||
if (!task.isSuccessful) {
|
||||
@ -49,14 +53,13 @@ class GetFirebasePushTokenWorker(val context: Context, workerParameters: WorkerP
|
||||
return@OnCompleteListener
|
||||
}
|
||||
|
||||
val token = task.result
|
||||
val pushToken = task.result
|
||||
Log.d(TAG, "Fetched firebase push token is: $pushToken")
|
||||
|
||||
appPreferences?.pushToken = token
|
||||
appPreferences.pushToken = pushToken
|
||||
|
||||
val data: Data =
|
||||
Data.Builder()
|
||||
.putString(PushRegistrationWorker.ORIGIN, "GetFirebasePushTokenWorker")
|
||||
.build()
|
||||
Data.Builder().putString(PushRegistrationWorker.ORIGIN, "GetFirebasePushTokenWorker").build()
|
||||
val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java)
|
||||
.setInputData(data)
|
||||
.build()
|
||||
@ -68,6 +71,6 @@ class GetFirebasePushTokenWorker(val context: Context, workerParameters: WorkerP
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "GetFirebasePushTokenWorker"
|
||||
private val TAG = GetFirebasePushTokenWorker::class.simpleName
|
||||
}
|
||||
}
|
||||
|
@ -79,10 +79,8 @@ class NCFirebaseMessagingService : FirebaseMessagingService() {
|
||||
|
||||
appPreferences.pushToken = token
|
||||
|
||||
val data: Data = Data.Builder().putString(
|
||||
PushRegistrationWorker.ORIGIN,
|
||||
"NCFirebaseMessagingService#onNewToken"
|
||||
).build()
|
||||
val data: Data =
|
||||
Data.Builder().putString(PushRegistrationWorker.ORIGIN, "NCFirebaseMessagingService#onNewToken").build()
|
||||
val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java)
|
||||
.setInputData(data)
|
||||
.build()
|
||||
|
@ -24,7 +24,7 @@
|
||||
package com.nextcloud.talk.utils
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.work.Data
|
||||
import android.util.Log
|
||||
import androidx.work.ExistingPeriodicWorkPolicy
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.PeriodicWorkRequest
|
||||
@ -36,7 +36,6 @@ import com.google.android.gms.security.ProviderInstaller
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.interfaces.ClosedInterface
|
||||
import com.nextcloud.talk.jobs.GetFirebasePushTokenWorker
|
||||
import com.nextcloud.talk.jobs.PushRegistrationWorker
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
@ -65,77 +64,43 @@ class ClosedInterfaceImpl : ClosedInterface, ProviderInstaller.ProviderInstallLi
|
||||
val api = GoogleApiAvailability.getInstance()
|
||||
val code =
|
||||
NextcloudTalkApplication.sharedApplication?.let {
|
||||
api.isGooglePlayServicesAvailable(
|
||||
it.applicationContext
|
||||
)
|
||||
api.isGooglePlayServicesAvailable(it.applicationContext)
|
||||
}
|
||||
return code == ConnectionResult.SUCCESS
|
||||
return if (code == ConnectionResult.SUCCESS) {
|
||||
true
|
||||
} else {
|
||||
Log.w(TAG, "GooglePlayServices are not available. Code:$code")
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun setUpPushTokenRegistration() {
|
||||
registerLocalToken()
|
||||
setUpPeriodicLocalTokenRegistration()
|
||||
val firebasePushTokenWorker = OneTimeWorkRequest.Builder(GetFirebasePushTokenWorker::class.java).build()
|
||||
WorkManager.getInstance().enqueue(firebasePushTokenWorker)
|
||||
|
||||
setUpPeriodicTokenRefreshFromFCM()
|
||||
}
|
||||
|
||||
private fun registerLocalToken() {
|
||||
val data: Data = Data.Builder().putString(
|
||||
PushRegistrationWorker.ORIGIN,
|
||||
"ClosedInterfaceImpl#registerLocalToken"
|
||||
)
|
||||
.build()
|
||||
val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java)
|
||||
.setInputData(data)
|
||||
.build()
|
||||
WorkManager.getInstance().enqueue(pushRegistrationWork)
|
||||
}
|
||||
|
||||
private fun setUpPeriodicLocalTokenRegistration() {
|
||||
val data: Data = Data.Builder().putString(
|
||||
PushRegistrationWorker.ORIGIN,
|
||||
"ClosedInterfaceImpl#setUpPeriodicLocalTokenRegistration"
|
||||
)
|
||||
.build()
|
||||
|
||||
val periodicTokenRegistration = PeriodicWorkRequest.Builder(
|
||||
PushRegistrationWorker::class.java,
|
||||
DAILY,
|
||||
TimeUnit.HOURS,
|
||||
FLEX_INTERVAL,
|
||||
TimeUnit.HOURS
|
||||
)
|
||||
.setInputData(data)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance()
|
||||
.enqueueUniquePeriodicWork(
|
||||
"periodicTokenRegistration",
|
||||
ExistingPeriodicWorkPolicy.REPLACE,
|
||||
periodicTokenRegistration
|
||||
)
|
||||
}
|
||||
|
||||
private fun setUpPeriodicTokenRefreshFromFCM() {
|
||||
val periodicTokenRefreshFromFCM = PeriodicWorkRequest.Builder(
|
||||
GetFirebasePushTokenWorker::class.java,
|
||||
MONTHLY,
|
||||
TimeUnit.DAYS,
|
||||
DAILY,
|
||||
TimeUnit.HOURS,
|
||||
FLEX_INTERVAL,
|
||||
TimeUnit.DAYS
|
||||
)
|
||||
.build()
|
||||
TimeUnit.HOURS
|
||||
).build()
|
||||
|
||||
WorkManager.getInstance()
|
||||
.enqueueUniquePeriodicWork(
|
||||
"periodicTokenRefreshFromFCM",
|
||||
ExistingPeriodicWorkPolicy.REPLACE,
|
||||
ExistingPeriodicWorkPolicy.UPDATE,
|
||||
periodicTokenRefreshFromFCM
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = ClosedInterfaceImpl::class.java.simpleName
|
||||
const val DAILY: Long = 24
|
||||
const val MONTHLY: Long = 30
|
||||
const val FLEX_INTERVAL: Long = 10
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
~ @author Mario Danic
|
||||
~ @author Marcel Hibbe
|
||||
~ Copyright (C) 2017-2019 Mario Danic <mario@lovelyhq.com>
|
||||
~ Copyright (C) 2021-2022 Marcel Hibbe <dev@mhibbe.de>
|
||||
~ Copyright (C) 2021-2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
@ -131,6 +131,44 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".account.ServerSelectionActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".account.WebViewLoginActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".account.AccountVerificationActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".account.SwitchAccountActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".conversationlist.ConversationsListActivity"
|
||||
android:theme="@style/AppTheme"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".chat.ChatActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".activities.CallActivity"
|
||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
|
||||
@ -152,17 +190,17 @@
|
||||
android:theme="@style/AppTheme.CallLauncher" />
|
||||
|
||||
<activity
|
||||
android:name=".activities.FullScreenImageActivity"
|
||||
android:name=".fullscreenfile.FullScreenImageActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:theme="@style/FullScreenImageTheme"/>
|
||||
|
||||
<activity
|
||||
android:name=".activities.FullScreenMediaActivity"
|
||||
android:name=".fullscreenfile.FullScreenMediaActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:theme="@style/FullScreenMediaTheme"/>
|
||||
|
||||
<activity
|
||||
android:name=".activities.FullScreenTextViewerActivity"
|
||||
android:name=".fullscreenfile.FullScreenTextViewerActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:theme="@style/FullScreenTextTheme"/>
|
||||
|
||||
@ -215,32 +253,10 @@
|
||||
android:name=".contacts.ContactsActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".chat.ChatActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".openconversations.ListOpenConversationsActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".conversationlist.ConversationsListActivity"
|
||||
android:theme="@style/AppTheme"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="*/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".lock.LockedActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
@ -3,6 +3,8 @@
|
||||
*
|
||||
* @author Mario Danic
|
||||
* @author Andy Scherzinger
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
|
||||
*
|
||||
@ -19,7 +21,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.nextcloud.talk.controllers
|
||||
package com.nextcloud.talk.account
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
@ -28,27 +30,25 @@ import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import autodagger.AutoInjector
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.bluelinelabs.logansquare.LoganSquare
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.controllers.base.BaseController
|
||||
import com.nextcloud.talk.controllers.util.viewBinding
|
||||
import com.nextcloud.talk.conversationlist.ConversationsListActivity
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.databinding.ControllerAccountVerificationBinding
|
||||
import com.nextcloud.talk.databinding.ActivityAccountVerificationBinding
|
||||
import com.nextcloud.talk.events.EventStatus
|
||||
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
||||
import com.nextcloud.talk.jobs.CapabilitiesWorker
|
||||
import com.nextcloud.talk.jobs.PushRegistrationWorker
|
||||
import com.nextcloud.talk.jobs.SignalingSettingsWorker
|
||||
import com.nextcloud.talk.jobs.WebsocketConnectionsWorker
|
||||
import com.nextcloud.talk.models.json.capabilities.Capabilities
|
||||
@ -63,6 +63,7 @@ import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_BASE_URL
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_INTERNAL_USER_ID
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ORIGINAL_PROTOCOL
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_PASSWORD
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USERNAME
|
||||
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
|
||||
@ -71,20 +72,15 @@ import io.reactivex.Observer
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import java.net.CookieManager
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
R.layout.controller_account_verification,
|
||||
args
|
||||
) {
|
||||
private val binding: ControllerAccountVerificationBinding? by viewBinding(
|
||||
ControllerAccountVerificationBinding::bind
|
||||
)
|
||||
class AccountVerificationActivity : BaseActivity() {
|
||||
|
||||
private lateinit var binding: ActivityAccountVerificationBinding
|
||||
|
||||
@Inject
|
||||
lateinit var ncApi: NcApi
|
||||
@ -95,9 +91,6 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
@Inject
|
||||
lateinit var cookieManager: CookieManager
|
||||
|
||||
@Inject
|
||||
lateinit var eventBus: EventBus
|
||||
|
||||
private var internalAccountId: Long = -1
|
||||
private val disposables: MutableList<Disposable> = ArrayList()
|
||||
private var baseUrl: String? = null
|
||||
@ -106,43 +99,53 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
private var isAccountImport = false
|
||||
private var originalProtocol: String? = null
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
eventBus.register(this)
|
||||
}
|
||||
|
||||
override fun onDetach(view: View) {
|
||||
super.onDetach(view)
|
||||
eventBus.unregister(this)
|
||||
}
|
||||
|
||||
override fun onViewBound(view: View) {
|
||||
super.onViewBound(view)
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
binding = ActivityAccountVerificationBinding.inflate(layoutInflater)
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
setContentView(binding.root)
|
||||
actionBar?.hide()
|
||||
setupPrimaryColors()
|
||||
|
||||
handleIntent()
|
||||
}
|
||||
|
||||
private fun handleIntent() {
|
||||
val extras = intent.extras!!
|
||||
baseUrl = extras.getString(KEY_BASE_URL)
|
||||
username = extras.getString(KEY_USERNAME)
|
||||
token = extras.getString(KEY_TOKEN)
|
||||
if (extras.containsKey(KEY_IS_ACCOUNT_IMPORT)) {
|
||||
isAccountImport = true
|
||||
}
|
||||
if (extras.containsKey(KEY_ORIGINAL_PROTOCOL)) {
|
||||
originalProtocol = extras.getString(KEY_ORIGINAL_PROTOCOL)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (
|
||||
isAccountImport &&
|
||||
!UriUtils.hasHttpProtocolPrefixed(baseUrl!!) ||
|
||||
isSameProtocol(baseUrl!!, originalProtocol!!)
|
||||
isNotSameProtocol(baseUrl!!, originalProtocol)
|
||||
) {
|
||||
determineBaseUrlProtocol(true)
|
||||
} else {
|
||||
checkEverything()
|
||||
findServerTalkApp()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSameProtocol(baseUrl: String, originalProtocol: String): Boolean {
|
||||
private fun isNotSameProtocol(baseUrl: String, originalProtocol: String?): Boolean {
|
||||
if (originalProtocol == null) {
|
||||
return true
|
||||
}
|
||||
return !TextUtils.isEmpty(originalProtocol) && !baseUrl.startsWith(originalProtocol)
|
||||
}
|
||||
|
||||
private fun checkEverything() {
|
||||
val credentials = ApiUtils.getCredentials(username, token)
|
||||
cookieManager.cookieStore.removeAll()
|
||||
findServerTalkApp(credentials)
|
||||
}
|
||||
|
||||
private fun determineBaseUrlProtocol(checkForcedHttps: Boolean) {
|
||||
cookieManager.cookieStore.removeAll()
|
||||
baseUrl = baseUrl!!.replace("http://", "").replace("https://", "")
|
||||
@ -166,20 +169,16 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
"http://$baseUrl"
|
||||
}
|
||||
if (isAccountImport) {
|
||||
router.replaceTopController(
|
||||
RouterTransaction.with(
|
||||
WebViewLoginController(
|
||||
baseUrl,
|
||||
false,
|
||||
username,
|
||||
""
|
||||
)
|
||||
)
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
val bundle = Bundle()
|
||||
bundle.putString(KEY_BASE_URL, baseUrl)
|
||||
bundle.putString(KEY_USERNAME, username)
|
||||
bundle.putString(KEY_PASSWORD, "")
|
||||
|
||||
val intent = Intent(context, WebViewLoginActivity::class.java)
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
} else {
|
||||
checkEverything()
|
||||
findServerTalkApp()
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +196,10 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
})
|
||||
}
|
||||
|
||||
private fun findServerTalkApp(credentials: String) {
|
||||
private fun findServerTalkApp() {
|
||||
val credentials = ApiUtils.getCredentials(username, token)
|
||||
cookieManager.cookieStore.removeAll()
|
||||
|
||||
ncApi.getCapabilities(credentials, ApiUtils.getUrlForCapabilities(baseUrl))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(object : Observer<CapabilitiesOverall> {
|
||||
@ -214,27 +216,24 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
if (hasTalk) {
|
||||
fetchProfile(credentials, capabilitiesOverall)
|
||||
} else {
|
||||
if (activity != null && resources != null) {
|
||||
activity!!.runOnUiThread {
|
||||
binding?.progressText?.setText(
|
||||
String.format(
|
||||
resources!!.getString(R.string.nc_nextcloud_talk_app_not_installed),
|
||||
resources!!.getString(R.string.nc_app_product_name)
|
||||
)
|
||||
if (resources != null) {
|
||||
runOnUiThread {
|
||||
binding.progressText.text = String.format(
|
||||
resources!!.getString(R.string.nc_nextcloud_talk_app_not_installed),
|
||||
resources!!.getString(R.string.nc_app_product_name)
|
||||
)
|
||||
}
|
||||
}
|
||||
ApplicationWideMessageHolder.getInstance().setMessageType(
|
||||
ApplicationWideMessageHolder.getInstance().messageType =
|
||||
ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK
|
||||
)
|
||||
abortVerification()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
if (activity != null && resources != null) {
|
||||
activity!!.runOnUiThread {
|
||||
binding?.progressText?.text = String.format(
|
||||
if (resources != null) {
|
||||
runOnUiThread {
|
||||
binding.progressText.text = String.format(
|
||||
resources!!.getString(R.string.nc_nextcloud_talk_app_not_installed),
|
||||
resources!!.getString(R.string.nc_app_product_name)
|
||||
)
|
||||
@ -263,7 +262,7 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
displayName = displayName,
|
||||
pushConfigurationState = null,
|
||||
capabilities = LoganSquare.serialize(capabilities),
|
||||
certificateAlias = appPreferences!!.temporaryClientCertAlias,
|
||||
certificateAlias = appPreferences.temporaryClientCertAlias,
|
||||
externalSignalingServer = null
|
||||
)
|
||||
)
|
||||
@ -277,11 +276,12 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
override fun onSuccess(user: User) {
|
||||
internalAccountId = user.id!!
|
||||
if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) {
|
||||
registerForPush()
|
||||
ClosedInterfaceImpl().setUpPushTokenRegistration()
|
||||
} else {
|
||||
activity!!.runOnUiThread {
|
||||
binding?.progressText?.text =
|
||||
""" ${binding?.progressText?.text}
|
||||
Log.w(TAG, "Skipping push registration.")
|
||||
runOnUiThread {
|
||||
binding.progressText.text =
|
||||
""" ${binding.progressText.text}
|
||||
${resources!!.getString(R.string.nc_push_disabled)}
|
||||
""".trimIndent()
|
||||
}
|
||||
@ -291,7 +291,7 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onError(e: Throwable) {
|
||||
binding?.progressText?.text = """ ${binding?.progressText?.text}""".trimIndent() +
|
||||
binding.progressText.text = """ ${binding.progressText.text}""".trimIndent() +
|
||||
resources!!.getString(R.string.nc_display_name_not_stored)
|
||||
abortVerification()
|
||||
}
|
||||
@ -328,14 +328,12 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
capabilities.ocs!!.data!!.capabilities!!
|
||||
)
|
||||
} else {
|
||||
if (activity != null) {
|
||||
activity!!.runOnUiThread {
|
||||
binding?.progressText?.text =
|
||||
"""
|
||||
${binding?.progressText?.text}
|
||||
${resources!!.getString(R.string.nc_display_name_not_fetched)}
|
||||
""".trimIndent()
|
||||
}
|
||||
runOnUiThread {
|
||||
binding.progressText.text =
|
||||
"""
|
||||
${binding.progressText.text}
|
||||
${resources!!.getString(R.string.nc_display_name_not_fetched)}
|
||||
""".trimIndent()
|
||||
}
|
||||
abortVerification()
|
||||
}
|
||||
@ -343,14 +341,12 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
override fun onError(e: Throwable) {
|
||||
if (activity != null) {
|
||||
activity!!.runOnUiThread {
|
||||
binding?.progressText?.text =
|
||||
"""
|
||||
${binding?.progressText?.text}
|
||||
${resources!!.getString(R.string.nc_display_name_not_fetched)}
|
||||
""".trimIndent()
|
||||
}
|
||||
runOnUiThread {
|
||||
binding.progressText.text =
|
||||
"""
|
||||
${binding.progressText.text}
|
||||
${resources!!.getString(R.string.nc_display_name_not_fetched)}
|
||||
""".trimIndent()
|
||||
}
|
||||
abortVerification()
|
||||
}
|
||||
@ -361,27 +357,16 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
})
|
||||
}
|
||||
|
||||
private fun registerForPush() {
|
||||
val data =
|
||||
Data.Builder()
|
||||
.putString(PushRegistrationWorker.ORIGIN, "AccountVerificationController#registerForPush")
|
||||
.build()
|
||||
val pushRegistrationWork =
|
||||
OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java)
|
||||
.setInputData(data)
|
||||
.build()
|
||||
WorkManager.getInstance().enqueue(pushRegistrationWork)
|
||||
}
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
@Subscribe(threadMode = ThreadMode.BACKGROUND)
|
||||
fun onMessageEvent(eventStatus: EventStatus) {
|
||||
Log.d(TAG, "caught EventStatus of type " + eventStatus.eventType.toString())
|
||||
if (eventStatus.eventType == EventStatus.EventType.PUSH_REGISTRATION) {
|
||||
if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood && activity != null) {
|
||||
activity!!.runOnUiThread {
|
||||
binding?.progressText?.text =
|
||||
if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
|
||||
runOnUiThread {
|
||||
binding.progressText.text =
|
||||
"""
|
||||
${binding?.progressText?.text}
|
||||
${binding.progressText.text}
|
||||
${resources!!.getString(R.string.nc_push_disabled)}
|
||||
""".trimIndent()
|
||||
}
|
||||
@ -389,14 +374,12 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
fetchAndStoreCapabilities()
|
||||
} else if (eventStatus.eventType == EventStatus.EventType.CAPABILITIES_FETCH) {
|
||||
if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
|
||||
if (activity != null) {
|
||||
activity!!.runOnUiThread {
|
||||
binding?.progressText?.text =
|
||||
"""
|
||||
${binding?.progressText?.text}
|
||||
${resources!!.getString(R.string.nc_capabilities_failed)}
|
||||
""".trimIndent()
|
||||
}
|
||||
runOnUiThread {
|
||||
binding.progressText.text =
|
||||
"""
|
||||
${binding.progressText.text}
|
||||
${resources!!.getString(R.string.nc_capabilities_failed)}
|
||||
""".trimIndent()
|
||||
}
|
||||
abortVerification()
|
||||
} else if (internalAccountId == eventStatus.userId && eventStatus.isAllGood) {
|
||||
@ -404,14 +387,12 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
}
|
||||
} else if (eventStatus.eventType == EventStatus.EventType.SIGNALING_SETTINGS) {
|
||||
if (internalAccountId == eventStatus.userId && !eventStatus.isAllGood) {
|
||||
if (activity != null) {
|
||||
activity!!.runOnUiThread {
|
||||
binding?.progressText?.text =
|
||||
"""
|
||||
${binding?.progressText?.text}
|
||||
${resources!!.getString(R.string.nc_external_server_failed)}
|
||||
""".trimIndent()
|
||||
}
|
||||
runOnUiThread {
|
||||
binding.progressText.text =
|
||||
"""
|
||||
${binding.progressText.text}
|
||||
${resources!!.getString(R.string.nc_external_server_failed)}
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
proceedWithLogin()
|
||||
@ -423,11 +404,11 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
Data.Builder()
|
||||
.putLong(KEY_INTERNAL_USER_ID, internalAccountId)
|
||||
.build()
|
||||
val pushNotificationWork =
|
||||
val capabilitiesWork =
|
||||
OneTimeWorkRequest.Builder(CapabilitiesWorker::class.java)
|
||||
.setInputData(userData)
|
||||
.build()
|
||||
WorkManager.getInstance().enqueue(pushNotificationWork)
|
||||
WorkManager.getInstance().enqueue(capabilitiesWork)
|
||||
}
|
||||
|
||||
private fun fetchAndStoreExternalSignalingSettings() {
|
||||
@ -435,19 +416,18 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
Data.Builder()
|
||||
.putLong(KEY_INTERNAL_USER_ID, internalAccountId)
|
||||
.build()
|
||||
val signalingSettings = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java)
|
||||
val signalingSettingsWorker = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java)
|
||||
.setInputData(userData)
|
||||
.build()
|
||||
val websocketConnectionsWorker = OneTimeWorkRequest.Builder(WebsocketConnectionsWorker::class.java).build()
|
||||
|
||||
WorkManager.getInstance(applicationContext!!)
|
||||
.beginWith(signalingSettings)
|
||||
.beginWith(signalingSettingsWorker)
|
||||
.then(websocketConnectionsWorker)
|
||||
.enqueue()
|
||||
}
|
||||
|
||||
private fun proceedWithLogin() {
|
||||
Log.d(TAG, "proceedWithLogin...")
|
||||
cookieManager.cookieStore.removeAll()
|
||||
|
||||
if (userManager.users.blockingGet().size == 1 ||
|
||||
@ -457,25 +437,25 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
Log.d(TAG, "userToSetAsActive: " + userToSetAsActive.username)
|
||||
|
||||
if (userManager.setUserAsActive(userToSetAsActive).blockingGet()) {
|
||||
if (activity != null) {
|
||||
activity!!.runOnUiThread {
|
||||
if (userManager.users.blockingGet().size == 1) {
|
||||
val intent = Intent(context, ConversationsListActivity::class.java)
|
||||
startActivity(intent)
|
||||
} else {
|
||||
if (isAccountImport) {
|
||||
ApplicationWideMessageHolder.getInstance().messageType =
|
||||
ApplicationWideMessageHolder.MessageType.ACCOUNT_WAS_IMPORTED
|
||||
}
|
||||
val intent = Intent(context, ConversationsListActivity::class.java)
|
||||
startActivity(intent)
|
||||
runOnUiThread {
|
||||
if (userManager.users.blockingGet().size == 1) {
|
||||
val intent = Intent(context, ConversationsListActivity::class.java)
|
||||
startActivity(intent)
|
||||
} else {
|
||||
if (isAccountImport) {
|
||||
ApplicationWideMessageHolder.getInstance().messageType =
|
||||
ApplicationWideMessageHolder.MessageType.ACCOUNT_WAS_IMPORTED
|
||||
}
|
||||
val intent = Intent(context, ConversationsListActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "failed to set active user")
|
||||
Snackbar.make(binding!!.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "continuing proceedWithLogin was skipped for this user")
|
||||
}
|
||||
}
|
||||
|
||||
@ -487,70 +467,70 @@ class AccountVerificationController(args: Bundle? = null) : BaseController(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView(view: View) {
|
||||
super.onDestroyView(view)
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
|
||||
}
|
||||
|
||||
public override fun onDestroy() {
|
||||
dispose()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
private fun abortVerification() {
|
||||
if (!isAccountImport) {
|
||||
if (internalAccountId != -1L) {
|
||||
val count = userManager.deleteUser(internalAccountId)
|
||||
if (count > 0) {
|
||||
activity?.runOnUiThread { Handler().postDelayed({ router.popToRoot() }, DELAY_IN_MILLIS) }
|
||||
}
|
||||
} else {
|
||||
activity?.runOnUiThread { Handler().postDelayed({ router.popToRoot() }, DELAY_IN_MILLIS) }
|
||||
}
|
||||
} else {
|
||||
ApplicationWideMessageHolder.getInstance().setMessageType(
|
||||
ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT
|
||||
)
|
||||
activity?.runOnUiThread {
|
||||
if (isAccountImport) {
|
||||
ApplicationWideMessageHolder.getInstance().messageType = ApplicationWideMessageHolder.MessageType
|
||||
.FAILED_TO_IMPORT_ACCOUNT
|
||||
runOnUiThread {
|
||||
Handler().postDelayed({
|
||||
if (router.hasRootController()) {
|
||||
if (activity != null) {
|
||||
router.popToRoot()
|
||||
}
|
||||
} else {
|
||||
if (userManager.users.blockingGet().isNotEmpty()) {
|
||||
val intent = Intent(context, ConversationsListActivity::class.java)
|
||||
startActivity(intent)
|
||||
} else {
|
||||
router.setRoot(
|
||||
RouterTransaction.with(ServerSelectionController())
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
}
|
||||
val intent = Intent(this, ServerSelectionActivity::class.java)
|
||||
startActivity(intent)
|
||||
}, DELAY_IN_MILLIS)
|
||||
}
|
||||
} else {
|
||||
if (internalAccountId != -1L) {
|
||||
runOnUiThread {
|
||||
deleteUserAndStartServerSelection(internalAccountId)
|
||||
}
|
||||
} else {
|
||||
runOnUiThread {
|
||||
Handler().postDelayed({
|
||||
val intent = Intent(this, ServerSelectionActivity::class.java)
|
||||
startActivity(intent)
|
||||
}, DELAY_IN_MILLIS)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private fun deleteUserAndStartServerSelection(userId: Long) {
|
||||
userManager.scheduleUserForDeletionWithId(userId).blockingGet()
|
||||
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
|
||||
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
|
||||
|
||||
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
|
||||
.observeForever { workInfo: WorkInfo ->
|
||||
|
||||
when (workInfo.state) {
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
val intent = Intent(this, ServerSelectionActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.resources.getString(R.string.nc_common_error_sorry),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
Log.e(TAG, "something went wrong when deleting user with id $userId")
|
||||
val intent = Intent(this, ServerSelectionActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "AccountVerification"
|
||||
private val TAG = AccountVerificationActivity::class.java.simpleName
|
||||
const val DELAY_IN_MILLIS: Long = 7500
|
||||
}
|
||||
|
||||
init {
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
if (args != null) {
|
||||
baseUrl = args.getString(KEY_BASE_URL)
|
||||
username = args.getString(KEY_USERNAME)
|
||||
token = args.getString(KEY_TOKEN)
|
||||
if (args.containsKey(KEY_IS_ACCOUNT_IMPORT)) {
|
||||
isAccountImport = true
|
||||
}
|
||||
if (args.containsKey(KEY_ORIGINAL_PROTOCOL)) {
|
||||
originalProtocol = args.getString(KEY_ORIGINAL_PROTOCOL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@
|
||||
*
|
||||
* @author Andy Scherzinger
|
||||
* @author Mario Danic
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
@ -19,7 +21,7 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.nextcloud.talk.controllers
|
||||
package com.nextcloud.talk.account
|
||||
|
||||
import android.accounts.Account
|
||||
import android.annotation.SuppressLint
|
||||
@ -34,24 +36,21 @@ import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import autodagger.AutoInjector
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.controllers.base.BaseController
|
||||
import com.nextcloud.talk.controllers.util.viewBinding
|
||||
import com.nextcloud.talk.databinding.ControllerServerSelectionBinding
|
||||
import com.nextcloud.talk.databinding.ActivityServerSelectionBinding
|
||||
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall
|
||||
import com.nextcloud.talk.models.json.generic.Status
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
import com.nextcloud.talk.utils.AccountUtils
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.nextcloud.talk.utils.UriUtils
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ADDITIONAL_ACCOUNT
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT
|
||||
import com.nextcloud.talk.utils.database.user.CapabilitiesUtilNew
|
||||
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
|
||||
@ -61,12 +60,12 @@ import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.security.cert.CertificateException
|
||||
import javax.inject.Inject
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class ServerSelectionController :
|
||||
BaseController(R.layout.controller_server_selection) {
|
||||
class ServerSelectionActivity : BaseActivity() {
|
||||
|
||||
private val binding: ControllerServerSelectionBinding? by viewBinding(ControllerServerSelectionBinding::bind)
|
||||
private lateinit var binding: ActivityServerSelectionBinding
|
||||
|
||||
@Inject
|
||||
lateinit var ncApi: NcApi
|
||||
@ -76,44 +75,40 @@ class ServerSelectionController :
|
||||
|
||||
private var statusQueryDisposable: Disposable? = null
|
||||
|
||||
fun onCertClick() {
|
||||
if (activity != null) {
|
||||
KeyChain.choosePrivateKeyAlias(
|
||||
activity!!,
|
||||
{ alias: String? ->
|
||||
if (alias != null) {
|
||||
appPreferences!!.temporaryClientCertAlias = alias
|
||||
} else {
|
||||
appPreferences!!.removeTemporaryClientCertAlias()
|
||||
}
|
||||
setCertTextView()
|
||||
},
|
||||
arrayOf("RSA", "EC"),
|
||||
null,
|
||||
null,
|
||||
-1,
|
||||
null
|
||||
)
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (intent.hasExtra(ADD_ADDITIONAL_ACCOUNT) && intent.getBooleanExtra(ADD_ADDITIONAL_ACCOUNT, false)) {
|
||||
finish()
|
||||
} else {
|
||||
finishAffinity()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewBound(view: View) {
|
||||
super.onViewBound(view)
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
if (activity != null) {
|
||||
activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
|
||||
binding = ActivityServerSelectionBinding.inflate(layoutInflater)
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
setContentView(binding.root)
|
||||
actionBar?.hide()
|
||||
setupPrimaryColors()
|
||||
|
||||
binding?.hostUrlInputHelperText?.text = String.format(
|
||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
binding.hostUrlInputHelperText.text = String.format(
|
||||
resources!!.getString(R.string.nc_server_helper_text),
|
||||
resources!!.getString(R.string.nc_server_product_name)
|
||||
)
|
||||
binding?.serverEntryTextInputLayout?.setEndIconOnClickListener { checkServerAndProceed() }
|
||||
binding.serverEntryTextInputLayout.setEndIconOnClickListener { checkServerAndProceed() }
|
||||
|
||||
if (resources!!.getBoolean(R.bool.hide_auth_cert)) {
|
||||
binding?.certTextView?.visibility = View.GONE
|
||||
binding.certTextView.visibility = View.GONE
|
||||
}
|
||||
|
||||
val loggedInUsers = userManager.users.blockingGet()
|
||||
@ -124,21 +119,54 @@ class ServerSelectionController :
|
||||
} else if (isAbleToShowProviderLink() && loggedInUsers.isEmpty()) {
|
||||
showVisitProvidersInfo()
|
||||
} else {
|
||||
binding?.importOrChooseProviderText?.visibility = View.INVISIBLE
|
||||
binding.importOrChooseProviderText.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
binding?.serverEntryTextInputEditText?.requestFocus()
|
||||
binding.serverEntryTextInputEditText.requestFocus()
|
||||
if (!TextUtils.isEmpty(resources!!.getString(R.string.weblogin_url))) {
|
||||
binding?.serverEntryTextInputEditText?.setText(resources!!.getString(R.string.weblogin_url))
|
||||
binding.serverEntryTextInputEditText.setText(resources!!.getString(R.string.weblogin_url))
|
||||
checkServerAndProceed()
|
||||
}
|
||||
binding?.serverEntryTextInputEditText?.setOnEditorActionListener { _: TextView?, i: Int, _: KeyEvent? ->
|
||||
binding.serverEntryTextInputEditText.setOnEditorActionListener { _: TextView?, i: Int, _: KeyEvent? ->
|
||||
if (i == EditorInfo.IME_ACTION_DONE) {
|
||||
checkServerAndProceed()
|
||||
}
|
||||
false
|
||||
}
|
||||
binding?.certTextView?.setOnClickListener { onCertClick() }
|
||||
binding.certTextView.setOnClickListener { onCertClick() }
|
||||
|
||||
if (ApplicationWideMessageHolder.getInstance().messageType != null) {
|
||||
if (ApplicationWideMessageHolder.getInstance().messageType
|
||||
== ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK
|
||||
) {
|
||||
setErrorText(resources!!.getString(R.string.nc_settings_no_talk_installed))
|
||||
} else if (ApplicationWideMessageHolder.getInstance().messageType
|
||||
== ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT
|
||||
) {
|
||||
setErrorText(resources!!.getString(R.string.nc_server_failed_to_import_account))
|
||||
}
|
||||
ApplicationWideMessageHolder.getInstance().messageType = null
|
||||
}
|
||||
setCertTextView()
|
||||
}
|
||||
|
||||
fun onCertClick() {
|
||||
KeyChain.choosePrivateKeyAlias(
|
||||
this,
|
||||
{ alias: String? ->
|
||||
if (alias != null) {
|
||||
appPreferences.temporaryClientCertAlias = alias
|
||||
} else {
|
||||
appPreferences.removeTemporaryClientCertAlias()
|
||||
}
|
||||
setCertTextView()
|
||||
},
|
||||
arrayOf("RSA", "EC"),
|
||||
null,
|
||||
null,
|
||||
-1,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
private fun isAbleToShowProviderLink(): Boolean {
|
||||
@ -152,41 +180,37 @@ class ServerSelectionController :
|
||||
)
|
||||
) {
|
||||
if (availableAccounts.size > 1) {
|
||||
binding?.importOrChooseProviderText?.text = String.format(
|
||||
binding.importOrChooseProviderText.text = String.format(
|
||||
resources!!.getString(R.string.nc_server_import_accounts),
|
||||
AccountUtils.getAppNameBasedOnPackage(resources!!.getString(R.string.nc_import_accounts_from))
|
||||
)
|
||||
} else {
|
||||
binding?.importOrChooseProviderText?.text = String.format(
|
||||
binding.importOrChooseProviderText.text = String.format(
|
||||
resources!!.getString(R.string.nc_server_import_account),
|
||||
AccountUtils.getAppNameBasedOnPackage(resources!!.getString(R.string.nc_import_accounts_from))
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (availableAccounts.size > 1) {
|
||||
binding?.importOrChooseProviderText?.text =
|
||||
binding.importOrChooseProviderText.text =
|
||||
resources!!.getString(R.string.nc_server_import_accounts_plain)
|
||||
} else {
|
||||
binding?.importOrChooseProviderText?.text =
|
||||
binding.importOrChooseProviderText.text =
|
||||
resources!!.getString(R.string.nc_server_import_account_plain)
|
||||
}
|
||||
}
|
||||
binding?.importOrChooseProviderText?.setOnClickListener {
|
||||
binding.importOrChooseProviderText.setOnClickListener {
|
||||
val bundle = Bundle()
|
||||
bundle.putBoolean(KEY_IS_ACCOUNT_IMPORT, true)
|
||||
router.pushController(
|
||||
RouterTransaction.with(
|
||||
SwitchAccountController(bundle)
|
||||
)
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
val intent = Intent(context, SwitchAccountActivity::class.java)
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showVisitProvidersInfo() {
|
||||
binding?.importOrChooseProviderText?.setText(R.string.nc_get_from_provider)
|
||||
binding?.importOrChooseProviderText?.setOnClickListener {
|
||||
binding.importOrChooseProviderText.setText(R.string.nc_get_from_provider)
|
||||
binding.importOrChooseProviderText.setOnClickListener {
|
||||
val browserIntent = Intent(
|
||||
Intent.ACTION_VIEW,
|
||||
Uri.parse(
|
||||
@ -206,11 +230,11 @@ class ServerSelectionController :
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun checkServerAndProceed() {
|
||||
dispose()
|
||||
var url: String = binding?.serverEntryTextInputEditText?.text.toString().trim { it <= ' ' }
|
||||
var url: String = binding.serverEntryTextInputEditText.text.toString().trim { it <= ' ' }
|
||||
showserverEntryProgressBar()
|
||||
if (binding?.importOrChooseProviderText?.visibility != View.INVISIBLE) {
|
||||
binding?.importOrChooseProviderText?.visibility = View.INVISIBLE
|
||||
binding?.certTextView?.visibility = View.INVISIBLE
|
||||
if (binding.importOrChooseProviderText.visibility != View.INVISIBLE) {
|
||||
binding.importOrChooseProviderText.visibility = View.INVISIBLE
|
||||
binding.certTextView.visibility = View.INVISIBLE
|
||||
}
|
||||
if (url.endsWith("/")) {
|
||||
url = url.substring(0, url.length - 1)
|
||||
@ -278,17 +302,17 @@ class ServerSelectionController :
|
||||
hideserverEntryProgressBar()
|
||||
}
|
||||
|
||||
if (binding?.importOrChooseProviderText?.visibility != View.INVISIBLE) {
|
||||
binding?.importOrChooseProviderText?.visibility = View.VISIBLE
|
||||
binding?.certTextView?.visibility = View.VISIBLE
|
||||
if (binding.importOrChooseProviderText.visibility != View.INVISIBLE) {
|
||||
binding.importOrChooseProviderText.visibility = View.VISIBLE
|
||||
binding.certTextView.visibility = View.VISIBLE
|
||||
}
|
||||
dispose()
|
||||
}
|
||||
}) {
|
||||
hideserverEntryProgressBar()
|
||||
if (binding?.importOrChooseProviderText?.visibility != View.INVISIBLE) {
|
||||
binding?.importOrChooseProviderText?.visibility = View.VISIBLE
|
||||
binding?.certTextView?.visibility = View.VISIBLE
|
||||
if (binding.importOrChooseProviderText.visibility != View.INVISIBLE) {
|
||||
binding.importOrChooseProviderText.visibility = View.VISIBLE
|
||||
binding.certTextView.visibility = View.VISIBLE
|
||||
}
|
||||
dispose()
|
||||
}
|
||||
@ -311,29 +335,25 @@ class ServerSelectionController :
|
||||
capabilities.spreedCapability?.features?.isNotEmpty() == true
|
||||
|
||||
if (hasTalk) {
|
||||
activity?.runOnUiThread {
|
||||
runOnUiThread {
|
||||
if (CapabilitiesUtilNew.isServerEOL(capabilities)) {
|
||||
if (resources != null) {
|
||||
activity!!.runOnUiThread {
|
||||
runOnUiThread {
|
||||
setErrorText(resources!!.getString(R.string.nc_settings_server_eol))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
router.pushController(
|
||||
RouterTransaction.with(
|
||||
WebViewLoginController(
|
||||
queryUrl.replace("/status.php", ""),
|
||||
false
|
||||
)
|
||||
)
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_BASE_URL, queryUrl.replace("/status.php", ""))
|
||||
|
||||
val intent = Intent(context, WebViewLoginActivity::class.java)
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (activity != null && resources != null) {
|
||||
activity!!.runOnUiThread {
|
||||
if (resources != null) {
|
||||
runOnUiThread {
|
||||
setErrorText(resources!!.getString(R.string.nc_server_unsupported))
|
||||
}
|
||||
}
|
||||
@ -342,8 +362,8 @@ class ServerSelectionController :
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e(TAG, "Error while checking capabilities", e)
|
||||
if (activity != null && resources != null) {
|
||||
activity!!.runOnUiThread {
|
||||
if (resources != null) {
|
||||
runOnUiThread {
|
||||
setErrorText(resources!!.getString(R.string.nc_common_error_sorry))
|
||||
}
|
||||
}
|
||||
@ -360,70 +380,29 @@ class ServerSelectionController :
|
||||
}
|
||||
|
||||
private fun setErrorText(text: String) {
|
||||
binding?.errorWrapper?.visibility = View.VISIBLE
|
||||
binding?.errorText?.text = text
|
||||
binding.errorWrapper.visibility = View.VISIBLE
|
||||
binding.errorText.text = text
|
||||
hideserverEntryProgressBar()
|
||||
}
|
||||
|
||||
private fun showserverEntryProgressBar() {
|
||||
binding?.errorWrapper?.visibility = View.INVISIBLE
|
||||
binding?.serverEntryProgressBar?.visibility = View.VISIBLE
|
||||
binding.errorWrapper.visibility = View.INVISIBLE
|
||||
binding.serverEntryProgressBar.visibility = View.VISIBLE
|
||||
}
|
||||
|
||||
private fun hideserverEntryProgressBar() {
|
||||
binding?.serverEntryProgressBar?.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
if (ApplicationWideMessageHolder.getInstance().messageType != null) {
|
||||
if (ApplicationWideMessageHolder.getInstance().messageType
|
||||
== ApplicationWideMessageHolder.MessageType.ACCOUNT_SCHEDULED_FOR_DELETION
|
||||
) {
|
||||
setErrorText(resources!!.getString(R.string.nc_account_scheduled_for_deletion))
|
||||
ApplicationWideMessageHolder.getInstance().messageType = null
|
||||
} else if (ApplicationWideMessageHolder.getInstance().messageType
|
||||
== ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK
|
||||
) {
|
||||
setErrorText(resources!!.getString(R.string.nc_settings_no_talk_installed))
|
||||
} else if (ApplicationWideMessageHolder.getInstance().messageType
|
||||
== ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT
|
||||
) {
|
||||
setErrorText(resources!!.getString(R.string.nc_server_failed_to_import_account))
|
||||
}
|
||||
ApplicationWideMessageHolder.getInstance().messageType = null
|
||||
}
|
||||
if (activity != null && resources != null) {
|
||||
DisplayUtils.applyColorToStatusBar(
|
||||
activity,
|
||||
ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
|
||||
)
|
||||
DisplayUtils.applyColorToNavigationBar(
|
||||
activity!!.window,
|
||||
ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
|
||||
)
|
||||
}
|
||||
setCertTextView()
|
||||
binding.serverEntryProgressBar.visibility = View.INVISIBLE
|
||||
}
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
private fun setCertTextView() {
|
||||
if (activity != null) {
|
||||
activity!!.runOnUiThread {
|
||||
if (!TextUtils.isEmpty(appPreferences!!.temporaryClientCertAlias)) {
|
||||
binding?.certTextView?.setText(R.string.nc_change_cert_auth)
|
||||
} else {
|
||||
binding?.certTextView?.setText(R.string.nc_configure_cert_auth)
|
||||
}
|
||||
hideserverEntryProgressBar()
|
||||
runOnUiThread {
|
||||
if (!TextUtils.isEmpty(appPreferences.temporaryClientCertAlias)) {
|
||||
binding.certTextView.setText(R.string.nc_change_cert_auth)
|
||||
} else {
|
||||
binding.certTextView.setText(R.string.nc_configure_cert_auth)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView(view: View) {
|
||||
super.onDestroyView(view)
|
||||
if (activity != null) {
|
||||
activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
|
||||
hideserverEntryProgressBar()
|
||||
}
|
||||
}
|
||||
|
||||
@ -443,7 +422,7 @@ class ServerSelectionController :
|
||||
get() = AppBarLayoutType.EMPTY
|
||||
|
||||
companion object {
|
||||
const val TAG = "ServerSelectionController"
|
||||
private val TAG = ServerSelectionActivity::class.java.simpleName
|
||||
const val MIN_SERVER_MAJOR_VERSION = 13
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@
|
||||
*
|
||||
* @author Mario Danic
|
||||
* @author Andy Scherzinger
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* Copyright (C) 2017 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
@ -22,25 +24,23 @@
|
||||
* Parts related to account import were either copied from or inspired by the great work done by David Luhmer at:
|
||||
* https://github.com/nextcloud/ownCloud-Account-Importer
|
||||
*/
|
||||
package com.nextcloud.talk.controllers
|
||||
package com.nextcloud.talk.account
|
||||
|
||||
import android.accounts.Account
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import autodagger.AutoInjector
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.adapters.items.AdvancedUserItem
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.controllers.base.BaseController
|
||||
import com.nextcloud.talk.controllers.util.viewBinding
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.databinding.ControllerGenericRvBinding
|
||||
import com.nextcloud.talk.databinding.ActivitySwitchAccountBinding
|
||||
import com.nextcloud.talk.models.ImportAccount
|
||||
import com.nextcloud.talk.models.json.participants.Participant
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
@ -56,14 +56,11 @@ import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import org.osmdroid.config.Configuration
|
||||
import java.net.CookieManager
|
||||
import javax.inject.Inject
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class SwitchAccountController(args: Bundle? = null) :
|
||||
BaseController(
|
||||
R.layout.controller_generic_rv,
|
||||
args
|
||||
) {
|
||||
private val binding: ControllerGenericRvBinding? by viewBinding(ControllerGenericRvBinding::bind)
|
||||
class SwitchAccountActivity : BaseActivity() {
|
||||
private lateinit var binding: ActivitySwitchAccountBinding
|
||||
|
||||
@Inject
|
||||
lateinit var userManager: UserManager
|
||||
@ -89,41 +86,52 @@ class SwitchAccountController(args: Bundle? = null) :
|
||||
|
||||
if (userManager.setUserAsActive(user).blockingGet()) {
|
||||
cookieManager.cookieStore.removeAll()
|
||||
if (activity != null) {
|
||||
activity!!.runOnUiThread { router.popCurrentController() }
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
binding = ActivitySwitchAccountBinding.inflate(layoutInflater)
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
setContentView(binding.root)
|
||||
setupActionBar()
|
||||
setupPrimaryColors()
|
||||
|
||||
Configuration.getInstance().load(context, PreferenceManager.getDefaultSharedPreferences(context))
|
||||
if (args?.containsKey(KEY_IS_ACCOUNT_IMPORT) == true) {
|
||||
isAccountImport = true
|
||||
}
|
||||
|
||||
handleIntent()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
router.popCurrentController()
|
||||
true
|
||||
private fun handleIntent() {
|
||||
intent.extras?.let {
|
||||
if (it.containsKey(KEY_IS_ACCOUNT_IMPORT)) {
|
||||
isAccountImport = true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewBound(view: View) {
|
||||
super.onViewBound(view)
|
||||
binding?.swipeRefreshLayout?.isEnabled = false
|
||||
private fun setupActionBar() {
|
||||
setSupportActionBar(binding.toolbar)
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||
supportActionBar?.setIcon(ColorDrawable(resources!!.getColor(R.color.transparent, null)))
|
||||
supportActionBar?.title = resources!!.getString(R.string.nc_select_an_account)
|
||||
}
|
||||
|
||||
actionBar?.show()
|
||||
@Suppress("Detekt.NestedBlockDepth")
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (adapter == null) {
|
||||
adapter = FlexibleAdapter(userItems, activity, false)
|
||||
adapter = FlexibleAdapter(userItems, this, false)
|
||||
var participant: Participant
|
||||
|
||||
if (!isAccountImport) {
|
||||
@ -166,11 +174,10 @@ class SwitchAccountController(args: Bundle? = null) :
|
||||
}
|
||||
|
||||
private fun prepareViews() {
|
||||
val layoutManager: LinearLayoutManager = SmoothScrollLinearLayoutManager(activity)
|
||||
binding?.recyclerView?.layoutManager = layoutManager
|
||||
binding?.recyclerView?.setHasFixedSize(true)
|
||||
binding?.recyclerView?.adapter = adapter
|
||||
binding?.swipeRefreshLayout?.isEnabled = false
|
||||
val layoutManager: LinearLayoutManager = SmoothScrollLinearLayoutManager(this)
|
||||
binding.recyclerView.layoutManager = layoutManager
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.adapter = adapter
|
||||
}
|
||||
|
||||
private fun reauthorizeFromImport(account: Account?) {
|
||||
@ -180,14 +187,9 @@ class SwitchAccountController(args: Bundle? = null) :
|
||||
bundle.putString(KEY_USERNAME, importAccount.getUsername())
|
||||
bundle.putString(KEY_TOKEN, importAccount.getToken())
|
||||
bundle.putBoolean(KEY_IS_ACCOUNT_IMPORT, true)
|
||||
router.pushController(
|
||||
RouterTransaction.with(AccountVerificationController(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
}
|
||||
|
||||
override val title: String
|
||||
get() =
|
||||
resources!!.getString(R.string.nc_select_an_account)
|
||||
val intent = Intent(context, AccountVerificationActivity::class.java)
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@
|
||||
*
|
||||
* @author Mario Danic
|
||||
* @author Andy Scherzinger
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* Copyright (C) 2017 Mario Danic (mario@lovelyhq.com)
|
||||
*
|
||||
@ -19,9 +21,10 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.nextcloud.talk.controllers
|
||||
package com.nextcloud.talk.account
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.graphics.Bitmap
|
||||
import android.net.http.SslError
|
||||
@ -40,34 +43,30 @@ import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.work.Data
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import autodagger.AutoInjector
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.activities.MainActivity
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.controllers.base.BaseController
|
||||
import com.nextcloud.talk.controllers.util.viewBinding
|
||||
import com.nextcloud.talk.databinding.ControllerWebViewLoginBinding
|
||||
import com.nextcloud.talk.databinding.ActivityWebViewLoginBinding
|
||||
import com.nextcloud.talk.events.CertificateEvent
|
||||
import com.nextcloud.talk.jobs.PushRegistrationWorker
|
||||
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
||||
import com.nextcloud.talk.models.LoginData
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_BASE_URL
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ORIGINAL_PROTOCOL
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USERNAME
|
||||
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder
|
||||
import com.nextcloud.talk.utils.ssl.TrustManager
|
||||
import de.cotech.hw.fido.WebViewFidoBridge
|
||||
import io.reactivex.disposables.Disposable
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import java.lang.reflect.Field
|
||||
import java.net.CookieManager
|
||||
import java.net.URLDecoder
|
||||
@ -78,11 +77,9 @@ import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class WebViewLoginController(args: Bundle? = null) : BaseController(
|
||||
R.layout.controller_web_view_login,
|
||||
args
|
||||
) {
|
||||
private val binding: ControllerWebViewLoginBinding? by viewBinding(ControllerWebViewLoginBinding::bind)
|
||||
class WebViewLoginActivity : BaseActivity() {
|
||||
|
||||
private lateinit var binding: ActivityWebViewLoginBinding
|
||||
|
||||
@Inject
|
||||
lateinit var userManager: UserManager
|
||||
@ -90,34 +87,26 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
|
||||
@Inject
|
||||
lateinit var trustManager: TrustManager
|
||||
|
||||
@Inject
|
||||
lateinit var eventBus: EventBus
|
||||
|
||||
@Inject
|
||||
lateinit var cookieManager: CookieManager
|
||||
|
||||
private var assembledPrefix: String? = null
|
||||
private var userQueryDisposable: Disposable? = null
|
||||
private var baseUrl: String? = null
|
||||
private var isPasswordUpdate = false
|
||||
private var reauthorizeAccount = false
|
||||
private var username: String? = null
|
||||
private var password: String? = null
|
||||
private var loginStep = 0
|
||||
private var automatedLoginAttempted = false
|
||||
private var webViewFidoBridge: WebViewFidoBridge? = null
|
||||
|
||||
constructor(baseUrl: String?, isPasswordUpdate: Boolean) : this() {
|
||||
this.baseUrl = baseUrl
|
||||
this.isPasswordUpdate = isPasswordUpdate
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
constructor(baseUrl: String?, isPasswordUpdate: Boolean, username: String?, password: String?) : this() {
|
||||
this.baseUrl = baseUrl
|
||||
this.isPasswordUpdate = isPasswordUpdate
|
||||
this.username = username
|
||||
this.password = password
|
||||
}
|
||||
|
||||
private val webLoginUserAgent: String
|
||||
get() = (
|
||||
Build.MANUFACTURER.substring(0, 1).toUpperCase(Locale.getDefault()) +
|
||||
@ -129,33 +118,57 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
|
||||
")"
|
||||
)
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
override fun onViewBound(view: View) {
|
||||
super.onViewBound(view)
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
binding = ActivityWebViewLoginBinding.inflate(layoutInflater)
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
setContentView(binding.root)
|
||||
actionBar?.hide()
|
||||
setupPrimaryColors()
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||
handleIntent()
|
||||
setupWebView()
|
||||
}
|
||||
|
||||
private fun handleIntent() {
|
||||
val extras = intent.extras!!
|
||||
baseUrl = extras.getString(KEY_BASE_URL)
|
||||
username = extras.getString(KEY_USERNAME)
|
||||
|
||||
if (extras.containsKey(BundleKeys.KEY_REAUTHORIZE_ACCOUNT)) {
|
||||
reauthorizeAccount = extras.getBoolean(BundleKeys.KEY_REAUTHORIZE_ACCOUNT)
|
||||
}
|
||||
|
||||
if (extras.containsKey(BundleKeys.KEY_PASSWORD)) {
|
||||
password = extras.getString(BundleKeys.KEY_PASSWORD)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun setupWebView() {
|
||||
assembledPrefix = resources!!.getString(R.string.nc_talk_login_scheme) + PROTOCOL_SUFFIX + "login/"
|
||||
binding?.webview?.settings?.allowFileAccess = false
|
||||
binding?.webview?.settings?.allowFileAccessFromFileURLs = false
|
||||
binding?.webview?.settings?.javaScriptEnabled = true
|
||||
binding?.webview?.settings?.javaScriptCanOpenWindowsAutomatically = false
|
||||
binding?.webview?.settings?.domStorageEnabled = true
|
||||
binding?.webview?.settings?.setUserAgentString(webLoginUserAgent)
|
||||
binding?.webview?.settings?.saveFormData = false
|
||||
binding?.webview?.settings?.savePassword = false
|
||||
binding?.webview?.settings?.setRenderPriority(WebSettings.RenderPriority.HIGH)
|
||||
binding?.webview?.clearCache(true)
|
||||
binding?.webview?.clearFormData()
|
||||
binding?.webview?.clearHistory()
|
||||
binding.webview.settings.allowFileAccess = false
|
||||
binding.webview.settings.allowFileAccessFromFileURLs = false
|
||||
binding.webview.settings.javaScriptEnabled = true
|
||||
binding.webview.settings.javaScriptCanOpenWindowsAutomatically = false
|
||||
binding.webview.settings.domStorageEnabled = true
|
||||
binding.webview.settings.userAgentString = webLoginUserAgent
|
||||
binding.webview.settings.saveFormData = false
|
||||
binding.webview.settings.savePassword = false
|
||||
binding.webview.settings.setRenderPriority(WebSettings.RenderPriority.HIGH)
|
||||
binding.webview.clearCache(true)
|
||||
binding.webview.clearFormData()
|
||||
binding.webview.clearHistory()
|
||||
WebView.clearClientCertPreferences(null)
|
||||
webViewFidoBridge = WebViewFidoBridge.createInstanceForWebView(activity as AppCompatActivity?, binding?.webview)
|
||||
CookieSyncManager.createInstance(activity)
|
||||
webViewFidoBridge = WebViewFidoBridge.createInstanceForWebView(this, binding.webview)
|
||||
CookieSyncManager.createInstance(this)
|
||||
android.webkit.CookieManager.getInstance().removeAllCookies(null)
|
||||
val headers: MutableMap<String, String> = HashMap()
|
||||
headers.put("OCS-APIRequest", "true")
|
||||
binding?.webview?.webViewClient = object : WebViewClient() {
|
||||
headers["OCS-APIRequest"] = "true"
|
||||
binding.webview.webViewClient = object : WebViewClient() {
|
||||
private var basePageLoaded = false
|
||||
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
|
||||
webViewFidoBridge?.delegateShouldInterceptRequest(view, request)
|
||||
@ -180,24 +193,24 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
loginStep++
|
||||
if (!basePageLoaded) {
|
||||
binding?.progressBar?.visibility = View.GONE
|
||||
binding?.webview?.visibility = View.VISIBLE
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.webview.visibility = View.VISIBLE
|
||||
|
||||
basePageLoaded = true
|
||||
}
|
||||
if (!TextUtils.isEmpty(username)) {
|
||||
if (loginStep == 1) {
|
||||
binding?.webview?.loadUrl(
|
||||
binding.webview.loadUrl(
|
||||
"javascript: {document.getElementsByClassName('login')[0].click(); };"
|
||||
)
|
||||
} else if (!automatedLoginAttempted) {
|
||||
automatedLoginAttempted = true
|
||||
if (TextUtils.isEmpty(password)) {
|
||||
binding?.webview?.loadUrl(
|
||||
binding.webview.loadUrl(
|
||||
"javascript:var justStore = document.getElementById('user').value = '$username';"
|
||||
)
|
||||
} else {
|
||||
binding?.webview?.loadUrl(
|
||||
binding.webview.loadUrl(
|
||||
"javascript: {" +
|
||||
"document.getElementById('user').value = '" + username + "';" +
|
||||
"document.getElementById('password').value = '" + password + "';" +
|
||||
@ -213,8 +226,8 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
|
||||
override fun onReceivedClientCertRequest(view: WebView, request: ClientCertRequest) {
|
||||
val user = userManager.currentUser.blockingGet()
|
||||
var alias: String? = null
|
||||
if (!isPasswordUpdate) {
|
||||
alias = appPreferences!!.temporaryClientCertAlias
|
||||
if (!reauthorizeAccount) {
|
||||
alias = appPreferences.temporaryClientCertAlias
|
||||
}
|
||||
if (TextUtils.isEmpty(alias) && user != null) {
|
||||
alias = user.clientCertificate
|
||||
@ -223,9 +236,9 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
|
||||
val finalAlias = alias
|
||||
Thread {
|
||||
try {
|
||||
val privateKey = KeyChain.getPrivateKey(activity!!, finalAlias!!)
|
||||
val privateKey = KeyChain.getPrivateKey(applicationContext, finalAlias!!)
|
||||
val certificates = KeyChain.getCertificateChain(
|
||||
activity!!,
|
||||
applicationContext,
|
||||
finalAlias
|
||||
)
|
||||
if (privateKey != null && certificates != null) {
|
||||
@ -241,16 +254,16 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
|
||||
}.start()
|
||||
} else {
|
||||
KeyChain.choosePrivateKeyAlias(
|
||||
activity!!,
|
||||
this@WebViewLoginActivity,
|
||||
{ chosenAlias: String? ->
|
||||
if (chosenAlias != null) {
|
||||
appPreferences!!.temporaryClientCertAlias = chosenAlias
|
||||
Thread {
|
||||
var privateKey: PrivateKey? = null
|
||||
try {
|
||||
privateKey = KeyChain.getPrivateKey(activity!!, chosenAlias)
|
||||
privateKey = KeyChain.getPrivateKey(applicationContext, chosenAlias)
|
||||
val certificates = KeyChain.getCertificateChain(
|
||||
activity!!,
|
||||
applicationContext,
|
||||
chosenAlias
|
||||
)
|
||||
if (privateKey != null && certificates != null) {
|
||||
@ -304,7 +317,7 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
|
||||
super.onReceivedError(view, errorCode, description, failingUrl)
|
||||
}
|
||||
}
|
||||
binding?.webview?.loadUrl("$baseUrl/index.php/login/flow", headers)
|
||||
binding.webview.loadUrl("$baseUrl/index.php/login/flow", headers)
|
||||
}
|
||||
|
||||
private fun dispose() {
|
||||
@ -318,82 +331,79 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
|
||||
val loginData = parseLoginData(assembledPrefix, dataString)
|
||||
if (loginData != null) {
|
||||
dispose()
|
||||
val currentUser = userManager.currentUser.blockingGet()
|
||||
var messageType: ApplicationWideMessageHolder.MessageType? = null
|
||||
if (!isPasswordUpdate &&
|
||||
userManager.checkIfUserExists(loginData.username!!, baseUrl!!).blockingGet()
|
||||
) {
|
||||
messageType = ApplicationWideMessageHolder.MessageType.ACCOUNT_UPDATED_NOT_ADDED
|
||||
}
|
||||
if (userManager.checkIfUserIsScheduledForDeletion(loginData.username!!, baseUrl!!).blockingGet()) {
|
||||
ApplicationWideMessageHolder.getInstance().messageType =
|
||||
ApplicationWideMessageHolder.MessageType.ACCOUNT_SCHEDULED_FOR_DELETION
|
||||
if (!isPasswordUpdate) {
|
||||
router.popToRoot()
|
||||
} else {
|
||||
router.popCurrentController()
|
||||
}
|
||||
}
|
||||
val finalMessageType = messageType
|
||||
cookieManager.cookieStore.removeAll()
|
||||
if (!isPasswordUpdate && finalMessageType == null) {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(KEY_USERNAME, loginData.username)
|
||||
bundle.putString(KEY_TOKEN, loginData.token)
|
||||
bundle.putString(KEY_BASE_URL, loginData.serverUrl)
|
||||
var protocol = ""
|
||||
if (baseUrl!!.startsWith("http://")) {
|
||||
protocol = "http://"
|
||||
} else if (baseUrl!!.startsWith("https://")) {
|
||||
protocol = "https://"
|
||||
}
|
||||
if (!TextUtils.isEmpty(protocol)) {
|
||||
bundle.putString(KEY_ORIGINAL_PROTOCOL, protocol)
|
||||
}
|
||||
router.pushController(
|
||||
RouterTransaction.with(AccountVerificationController(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
} else {
|
||||
if (isPasswordUpdate) {
|
||||
if (currentUser != null) {
|
||||
currentUser.clientCertificate = appPreferences!!.temporaryClientCertAlias
|
||||
currentUser.token = loginData.token
|
||||
val rowsUpdated = userManager.updateOrCreateUser(currentUser).blockingGet()
|
||||
Log.d(TAG, "User rows updated: $rowsUpdated")
|
||||
|
||||
if (finalMessageType != null) {
|
||||
ApplicationWideMessageHolder.getInstance().messageType = finalMessageType
|
||||
}
|
||||
|
||||
val data = Data.Builder().putString(
|
||||
PushRegistrationWorker.ORIGIN,
|
||||
"WebViewLoginController#parseAndLoginFromWebView"
|
||||
).build()
|
||||
|
||||
val pushRegistrationWork = OneTimeWorkRequest.Builder(
|
||||
PushRegistrationWorker::class.java
|
||||
)
|
||||
.setInputData(data)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance().enqueue(pushRegistrationWork)
|
||||
router.popCurrentController()
|
||||
}
|
||||
if (userManager.checkIfUserIsScheduledForDeletion(loginData.username!!, baseUrl!!).blockingGet()) {
|
||||
Log.e(TAG, "Tried to add already existing user who is scheduled for deletion.")
|
||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
// however the user is not yet deleted, just start AccountRemovalWorker again to make sure to delete it.
|
||||
startAccountRemovalWorkerAndRestartApp()
|
||||
} else if (userManager.checkIfUserExists(loginData.username!!, baseUrl!!).blockingGet()) {
|
||||
if (reauthorizeAccount) {
|
||||
updateUserAndRestartApp(loginData)
|
||||
} else {
|
||||
if (finalMessageType != null) {
|
||||
// FIXME when the user registers a new account that was setup before (aka
|
||||
// ApplicationWideMessageHolder.MessageType.ACCOUNT_UPDATED_NOT_ADDED)
|
||||
// The token is not updated in the database and therefore the account not visible/usable
|
||||
ApplicationWideMessageHolder.getInstance().messageType = finalMessageType
|
||||
}
|
||||
router.popToRoot()
|
||||
Log.w(TAG, "It was tried to add an account that account already exists. Skipped user creation.")
|
||||
restartApp()
|
||||
}
|
||||
} else {
|
||||
startAccountVerification(loginData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAccountVerification(loginData: LoginData) {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(KEY_USERNAME, loginData.username)
|
||||
bundle.putString(KEY_TOKEN, loginData.token)
|
||||
bundle.putString(KEY_BASE_URL, loginData.serverUrl)
|
||||
var protocol = ""
|
||||
if (baseUrl!!.startsWith("http://")) {
|
||||
protocol = "http://"
|
||||
} else if (baseUrl!!.startsWith("https://")) {
|
||||
protocol = "https://"
|
||||
}
|
||||
if (!TextUtils.isEmpty(protocol)) {
|
||||
bundle.putString(KEY_ORIGINAL_PROTOCOL, protocol)
|
||||
}
|
||||
val intent = Intent(context, AccountVerificationActivity::class.java)
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun restartApp() {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun updateUserAndRestartApp(loginData: LoginData) {
|
||||
val currentUser = userManager.currentUser.blockingGet()
|
||||
if (currentUser != null) {
|
||||
currentUser.clientCertificate = appPreferences.temporaryClientCertAlias
|
||||
currentUser.token = loginData.token
|
||||
val rowsUpdated = userManager.updateOrCreateUser(currentUser).blockingGet()
|
||||
Log.d(TAG, "User rows updated: $rowsUpdated")
|
||||
restartApp()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAccountRemovalWorkerAndRestartApp() {
|
||||
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
|
||||
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
|
||||
|
||||
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
|
||||
.observeForever { workInfo: WorkInfo ->
|
||||
|
||||
when (workInfo.state) {
|
||||
WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
|
||||
restartApp()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseLoginData(prefix: String?, dataString: String): LoginData? {
|
||||
if (dataString.length < prefix!!.length) {
|
||||
return null
|
||||
@ -432,30 +442,11 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
super.onAttach(view)
|
||||
if (activity != null && resources != null) {
|
||||
DisplayUtils.applyColorToStatusBar(
|
||||
activity,
|
||||
ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
|
||||
)
|
||||
DisplayUtils.applyColorToNavigationBar(
|
||||
activity!!.window,
|
||||
ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
dispose()
|
||||
}
|
||||
|
||||
override fun onDestroyView(view: View) {
|
||||
super.onDestroyView(view)
|
||||
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
|
||||
}
|
||||
|
||||
init {
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
}
|
||||
@ -464,7 +455,7 @@ class WebViewLoginController(args: Bundle? = null) : BaseController(
|
||||
get() = AppBarLayoutType.EMPTY
|
||||
|
||||
companion object {
|
||||
const val TAG = "WebViewLoginController"
|
||||
private val TAG = WebViewLoginActivity::class.java.simpleName
|
||||
private const val PROTOCOL_SUFFIX = "://"
|
||||
private const val LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":"
|
||||
private const val PARAMETER_COUNT = 3
|
@ -16,7 +16,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.nextcloud.talk.controllers.base.providers;
|
||||
package com.nextcloud.talk.activities;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
|
@ -24,17 +24,26 @@ package com.nextcloud.talk.activities
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.webkit.SslErrorHandler
|
||||
import android.widget.EditText
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import autodagger.AutoInjector
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.account.AccountVerificationActivity
|
||||
import com.nextcloud.talk.account.ServerSelectionActivity
|
||||
import com.nextcloud.talk.account.SwitchAccountActivity
|
||||
import com.nextcloud.talk.account.WebViewLoginActivity
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.events.CertificateEvent
|
||||
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
||||
@ -53,7 +62,9 @@ import javax.inject.Inject
|
||||
open class BaseActivity : AppCompatActivity() {
|
||||
|
||||
enum class AppBarLayoutType {
|
||||
TOOLBAR, SEARCH_BAR, EMPTY
|
||||
TOOLBAR,
|
||||
SEARCH_BAR,
|
||||
EMPTY
|
||||
}
|
||||
|
||||
@Inject
|
||||
@ -77,6 +88,8 @@ open class BaseActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
cleanTempCertPreference()
|
||||
}
|
||||
|
||||
public override fun onStart() {
|
||||
@ -87,6 +100,11 @@ open class BaseActivity : AppCompatActivity() {
|
||||
public override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.isKeyboardIncognito) {
|
||||
val viewGroup = (findViewById<View>(android.R.id.content) as ViewGroup).getChildAt(0) as ViewGroup
|
||||
disableKeyboardPersonalisedLearning(viewGroup)
|
||||
}
|
||||
|
||||
if (appPreferences.isScreenSecured) {
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||
} else {
|
||||
@ -104,6 +122,19 @@ open class BaseActivity : AppCompatActivity() {
|
||||
colorizeNavigationBar()
|
||||
}
|
||||
|
||||
fun setupPrimaryColors() {
|
||||
if (resources != null) {
|
||||
DisplayUtils.applyColorToStatusBar(
|
||||
this,
|
||||
ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
|
||||
)
|
||||
DisplayUtils.applyColorToNavigationBar(
|
||||
window,
|
||||
ResourcesCompat.getColor(resources!!, R.color.colorPrimary, null)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
open fun colorizeStatusBar() {
|
||||
if (resources != null) {
|
||||
if (appBarLayoutType == AppBarLayoutType.SEARCH_BAR) {
|
||||
@ -123,7 +154,23 @@ open class BaseActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
fun showCertificateDialog(
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private fun disableKeyboardPersonalisedLearning(viewGroup: ViewGroup) {
|
||||
var view: View?
|
||||
var editText: EditText
|
||||
for (i in 0 until viewGroup.childCount) {
|
||||
view = viewGroup.getChildAt(i)
|
||||
if (view is EditText) {
|
||||
editText = view
|
||||
editText.imeOptions = editText.imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
|
||||
} else if (view is ViewGroup) {
|
||||
disableKeyboardPersonalisedLearning(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("Detekt.NestedBlockDepth")
|
||||
private fun showCertificateDialog(
|
||||
cert: X509Certificate,
|
||||
trustManager: TrustManager,
|
||||
sslErrorHandler: SslErrorHandler?
|
||||
@ -160,15 +207,17 @@ open class BaseActivity : AppCompatActivity() {
|
||||
validUntil
|
||||
)
|
||||
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(this)
|
||||
.setIcon(viewThemeUtils.dialog.colorMaterialAlertDialogIcon(context, R.drawable.ic_security_white_24dp))
|
||||
.setTitle(R.string.nc_certificate_dialog_title)
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(this).setIcon(
|
||||
viewThemeUtils.dialog.colorMaterialAlertDialogIcon(
|
||||
context,
|
||||
R.drawable.ic_security_white_24dp
|
||||
)
|
||||
).setTitle(R.string.nc_certificate_dialog_title)
|
||||
.setMessage(dialogText)
|
||||
.setPositiveButton(R.string.nc_yes) { _, _ ->
|
||||
trustManager.addCertInTrustStore(cert)
|
||||
sslErrorHandler?.proceed()
|
||||
}
|
||||
.setNegativeButton(R.string.nc_no) { _, _ ->
|
||||
}.setNegativeButton(R.string.nc_no) { _, _ ->
|
||||
sslErrorHandler?.cancel()
|
||||
}
|
||||
|
||||
@ -185,12 +234,23 @@ open class BaseActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun cleanTempCertPreference() {
|
||||
val temporaryClassNames: MutableList<String> = ArrayList()
|
||||
temporaryClassNames.add(ServerSelectionActivity::class.java.name)
|
||||
temporaryClassNames.add(AccountVerificationActivity::class.java.name)
|
||||
temporaryClassNames.add(WebViewLoginActivity::class.java.name)
|
||||
temporaryClassNames.add(SwitchAccountActivity::class.java.name)
|
||||
if (!temporaryClassNames.contains(javaClass.name)) {
|
||||
appPreferences.removeTemporaryClientCertAlias()
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
fun onMessageEvent(event: CertificateEvent) {
|
||||
showCertificateDialog(event.x509Certificate, event.magicTrustManager, event.sslErrorHandler)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = "BaseActivity"
|
||||
private val TAG = BaseActivity::class.java.simpleName
|
||||
}
|
||||
}
|
||||
|
@ -1050,10 +1050,7 @@ class CallActivity : CallBaseActivity() {
|
||||
private val isConnectionEstablished: Boolean
|
||||
get() = currentCallStatus === CallStatus.JOINED || currentCallStatus === CallStatus.IN_CONVERSATION
|
||||
|
||||
private fun onAudioManagerDevicesChanged(
|
||||
currentDevice: AudioDevice,
|
||||
availableDevices: Set<AudioDevice>
|
||||
) {
|
||||
private fun onAudioManagerDevicesChanged(currentDevice: AudioDevice, availableDevices: Set<AudioDevice>) {
|
||||
Log.d(TAG, "onAudioManagerDevicesChanged: $availableDevices, currentDevice: $currentDevice")
|
||||
val shouldDisableProximityLock =
|
||||
currentDevice == AudioDevice.WIRED_HEADSET ||
|
||||
@ -1529,10 +1526,7 @@ class CallActivity : CallBaseActivity() {
|
||||
})
|
||||
}
|
||||
|
||||
private fun addIceServers(
|
||||
signalingSettingsOverall: SignalingSettingsOverall,
|
||||
apiVersion: Int
|
||||
) {
|
||||
private fun addIceServers(signalingSettingsOverall: SignalingSettingsOverall, apiVersion: Int) {
|
||||
if (signalingSettingsOverall.ocs!!.settings!!.stunServers != null) {
|
||||
val stunServers = signalingSettingsOverall.ocs!!.settings!!.stunServers
|
||||
if (apiVersion == ApiUtils.APIv3) {
|
||||
@ -2049,7 +2043,7 @@ class CallActivity : CallBaseActivity() {
|
||||
}
|
||||
|
||||
override fun onNext(genericOverall: GenericOverall) {
|
||||
if (!switchToRoomToken.isEmpty()) {
|
||||
if (switchToRoomToken.isNotEmpty()) {
|
||||
val intent = Intent(context, ChatActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
val bundle = Bundle()
|
||||
@ -2070,8 +2064,8 @@ class CallActivity : CallBaseActivity() {
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Snackbar.make(binding!!.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
Log.e(TAG, "Error while leaving the call", e)
|
||||
Log.w(TAG, "Something went wrong when leaving the call", e)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
@ -3035,11 +3029,7 @@ class CallActivity : CallBaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updatePictureInPictureActions(
|
||||
@DrawableRes iconId: Int,
|
||||
title: String?,
|
||||
requestCode: Int
|
||||
) {
|
||||
private fun updatePictureInPictureActions(@DrawableRes iconId: Int, title: String?, requestCode: Int) {
|
||||
if (isGreaterEqualOreo && isPipModePossible) {
|
||||
val actions = ArrayList<RemoteAction>()
|
||||
val icon = Icon.createWithResource(this, iconId)
|
||||
@ -3096,7 +3086,7 @@ class CallActivity : CallBaseActivity() {
|
||||
}
|
||||
|
||||
override fun suppressFitsSystemWindows() {
|
||||
binding!!.controllerCallLayout.fitsSystemWindows = false
|
||||
binding!!.callLayout.fitsSystemWindows = false
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
|
@ -24,5 +24,12 @@ import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
enum class CallStatus : Parcelable {
|
||||
CONNECTING, CALLING_TIMEOUT, JOINED, IN_CONVERSATION, RECONNECTING, OFFLINE, LEAVING, PUBLISHER_FAILED
|
||||
CONNECTING,
|
||||
CALLING_TIMEOUT,
|
||||
JOINED,
|
||||
IN_CONVERSATION,
|
||||
RECONNECTING,
|
||||
OFFLINE,
|
||||
LEAVING,
|
||||
PUBLISHER_FAILED
|
||||
}
|
||||
|
@ -32,26 +32,20 @@ import android.os.Bundle
|
||||
import android.provider.ContactsContract
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import autodagger.AutoInjector
|
||||
import com.bluelinelabs.conductor.Conductor
|
||||
import com.bluelinelabs.conductor.Router
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler
|
||||
import com.bluelinelabs.conductor.changehandler.VerticalChangeHandler
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.nextcloud.talk.BuildConfig
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.account.ServerSelectionActivity
|
||||
import com.nextcloud.talk.account.WebViewLoginActivity
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.callnotification.CallNotificationActivity
|
||||
import com.nextcloud.talk.chat.ChatActivity
|
||||
import com.nextcloud.talk.controllers.ServerSelectionController
|
||||
import com.nextcloud.talk.controllers.WebViewLoginController
|
||||
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
|
||||
import com.nextcloud.talk.conversationlist.ConversationsListActivity
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.databinding.ActivityMainBinding
|
||||
@ -59,9 +53,9 @@ import com.nextcloud.talk.lock.LockedActivity
|
||||
import com.nextcloud.talk.models.json.conversations.RoomOverall
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.ClosedInterfaceImpl
|
||||
import com.nextcloud.talk.utils.SecurityUtils
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ACCOUNT
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ROOM_TOKEN
|
||||
import io.reactivex.Observer
|
||||
import io.reactivex.SingleObserver
|
||||
@ -80,17 +74,12 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
||||
@Inject
|
||||
lateinit var userManager: UserManager
|
||||
|
||||
private var router: Router? = null
|
||||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
if (!router!!.handleBack()) {
|
||||
finish()
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
Log.d(TAG, "onCreate: Activity: " + System.identityHashCode(this).toString())
|
||||
|
||||
@ -111,8 +100,6 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
||||
|
||||
setSupportActionBar(binding.toolbar)
|
||||
|
||||
router = Conductor.attachRouter(this, binding.controllerContainer, savedInstanceState)
|
||||
|
||||
handleIntent(intent)
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||
@ -128,28 +115,24 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchLoginScreen() {
|
||||
if (!TextUtils.isEmpty(resources.getString(R.string.weblogin_url))) {
|
||||
router!!.pushController(
|
||||
RouterTransaction.with(
|
||||
WebViewLoginController(resources.getString(R.string.weblogin_url), false)
|
||||
)
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
private fun launchServerSelection() {
|
||||
if (isBrandingUrlSet()) {
|
||||
val intent = Intent(context, WebViewLoginActivity::class.java)
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_BASE_URL, resources.getString(R.string.weblogin_url))
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
} else {
|
||||
router!!.setRoot(
|
||||
RouterTransaction.with(ServerSelectionController())
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler())
|
||||
)
|
||||
val intent = Intent(context, ServerSelectionActivity::class.java)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun isBrandingUrlSet() = !TextUtils.isEmpty(resources.getString(R.string.weblogin_url))
|
||||
|
||||
override fun onStart() {
|
||||
Log.d(TAG, "onStart: Activity: " + System.identityHashCode(this).toString())
|
||||
super.onStart()
|
||||
logRouterBackStack(router!!)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@ -178,14 +161,6 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
fun addAccount() {
|
||||
router!!.pushController(
|
||||
RouterTransaction.with(ServerSelectionController())
|
||||
.pushChangeHandler(VerticalChangeHandler())
|
||||
.popChangeHandler(VerticalChangeHandler())
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleActionFromContact(intent: Intent) {
|
||||
if (intent.action == Intent.ACTION_VIEW && intent.data != null) {
|
||||
val cursor = contentResolver.query(intent.data!!, null, null, null, null)
|
||||
@ -209,7 +184,7 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
||||
startConversation(user)
|
||||
} else {
|
||||
Snackbar.make(
|
||||
binding.controllerContainer,
|
||||
binding.root,
|
||||
R.string.nc_phone_book_integration_account_not_found,
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
@ -283,28 +258,18 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
||||
}
|
||||
|
||||
if (user != null && userManager.setUserAsActive(user).blockingGet()) {
|
||||
// this should be avoided (it's still from conductor architecture). activities should be opened directly.
|
||||
if (intent.hasExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL)) {
|
||||
if (intent.getBooleanExtra(BundleKeys.KEY_FROM_NOTIFICATION_START_CALL, false)) {
|
||||
if (!router!!.hasRootController()) {
|
||||
openConversationList()
|
||||
}
|
||||
val callNotificationIntent = Intent(this, CallNotificationActivity::class.java)
|
||||
intent.extras?.let { callNotificationIntent.putExtras(it) }
|
||||
startActivity(callNotificationIntent)
|
||||
} else {
|
||||
logRouterBackStack(router!!)
|
||||
|
||||
val chatIntent = Intent(context, ChatActivity::class.java)
|
||||
chatIntent.putExtras(intent.extras!!)
|
||||
startActivity(chatIntent)
|
||||
|
||||
logRouterBackStack(router!!)
|
||||
}
|
||||
}
|
||||
} else if (intent.hasExtra(ADD_ACCOUNT) && intent.getBooleanExtra(ADD_ACCOUNT, false)) {
|
||||
addAccount()
|
||||
} else if (!router!!.hasRootController()) {
|
||||
} else {
|
||||
if (!appPreferences.isDbRoomMigrated) {
|
||||
appPreferences.isDbRoomMigrated = true
|
||||
}
|
||||
@ -316,36 +281,30 @@ class MainActivity : BaseActivity(), ActionBarProvider {
|
||||
|
||||
override fun onSuccess(users: List<User>) {
|
||||
if (users.isNotEmpty()) {
|
||||
ClosedInterfaceImpl().setUpPushTokenRegistration()
|
||||
runOnUiThread {
|
||||
openConversationList()
|
||||
}
|
||||
} else {
|
||||
runOnUiThread {
|
||||
launchLoginScreen()
|
||||
launchServerSelection()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e(TAG, "Error loading existing users", e)
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.resources.getString(R.string.nc_common_error_sorry),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun logRouterBackStack(router: Router) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
val backstack = router.backstack
|
||||
var routerTransaction: RouterTransaction?
|
||||
Log.d(TAG, " backstack size: " + router.backstackSize)
|
||||
for (i in 0 until router.backstackSize) {
|
||||
routerTransaction = backstack[i]
|
||||
Log.d(TAG, " controller: " + routerTransaction.controller)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MainActivity"
|
||||
private val TAG = MainActivity::class.java.simpleName
|
||||
}
|
||||
}
|
||||
|
@ -203,9 +203,7 @@ class ConversationItem(
|
||||
}
|
||||
}
|
||||
|
||||
private fun shouldLoadAvatar(
|
||||
holder: ConversationItemViewHolder
|
||||
): Boolean {
|
||||
private fun shouldLoadAvatar(holder: ConversationItemViewHolder): Boolean {
|
||||
return when (model.objectType) {
|
||||
Conversation.ObjectType.SHARE_PASSWORD -> {
|
||||
holder.binding.dialogAvatar.setImageDrawable(
|
||||
@ -237,10 +235,7 @@ class ConversationItem(
|
||||
}
|
||||
}
|
||||
|
||||
private fun setLastMessage(
|
||||
holder: ConversationItemViewHolder,
|
||||
appContext: Context
|
||||
) {
|
||||
private fun setLastMessage(holder: ConversationItemViewHolder, appContext: Context) {
|
||||
if (model.lastMessage != null) {
|
||||
holder.binding.dialogDate.visibility = View.VISIBLE
|
||||
holder.binding.dialogDate.text = DateUtils.getRelativeTimeSpanString(
|
||||
|
@ -95,10 +95,11 @@ data class MessageResultItem constructor(
|
||||
const val VIEW_TYPE = FlexibleItemViewType.MESSAGE_RESULT_ITEM
|
||||
}
|
||||
|
||||
override fun getHeader(): GenericTextHeaderItem = MessagesTextHeaderItem(context, viewThemeUtils)
|
||||
.apply {
|
||||
isHidden = showHeader // FlexibleAdapter needs this hack for some reason
|
||||
}
|
||||
override fun getHeader(): GenericTextHeaderItem =
|
||||
MessagesTextHeaderItem(context, viewThemeUtils)
|
||||
.apply {
|
||||
isHidden = showHeader // FlexibleAdapter needs this hack for some reason
|
||||
}
|
||||
|
||||
override fun setHeader(header: GenericTextHeaderItem?) {
|
||||
// nothing, header is always the same
|
||||
|
@ -124,6 +124,6 @@ class CallStartedViewHolder(incomingView: View, payload: Any) :
|
||||
}
|
||||
|
||||
companion object {
|
||||
var TAG: String? = CallStartedViewHolder::class.simpleName
|
||||
val TAG: String? = CallStartedViewHolder::class.simpleName
|
||||
}
|
||||
}
|
||||
|
@ -231,6 +231,6 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) :
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = NextcloudTalkApplication::class.java.simpleName
|
||||
private val TAG = IncomingPollMessageViewHolder::class.java.simpleName
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,8 @@
|
||||
|
||||
package com.nextcloud.talk.adapters.messages;
|
||||
|
||||
import android.text.Spanned;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
@ -33,6 +35,10 @@ import com.nextcloud.talk.R;
|
||||
import com.nextcloud.talk.databinding.ItemCustomIncomingPreviewMessageBinding;
|
||||
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage;
|
||||
import com.nextcloud.talk.utils.TextMatchers;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
@ -49,7 +55,49 @@ public class IncomingPreviewMessageViewHolder extends PreviewMessageViewHolder {
|
||||
@Override
|
||||
public void onBind(@NonNull ChatMessage message) {
|
||||
super.onBind(message);
|
||||
if(!message.isVoiceMessage()
|
||||
&& !Objects.equals(message.getMessage(), "{file}")
|
||||
) {
|
||||
Spanned processedMessageText = null;
|
||||
binding.incomingPreviewMessageBubble.setBackgroundResource(R.drawable.shape_grouped_incoming_message);
|
||||
if (viewThemeUtils != null ) {
|
||||
processedMessageText = messageUtils.enrichChatMessageText(
|
||||
binding.messageCaption.getContext(),
|
||||
message,
|
||||
true,
|
||||
viewThemeUtils);
|
||||
viewThemeUtils.talk.themeIncomingMessageBubble(binding.incomingPreviewMessageBubble, true, false);
|
||||
}
|
||||
|
||||
if (processedMessageText != null) {
|
||||
processedMessageText = messageUtils.processMessageParameters(
|
||||
binding.messageCaption.getContext(),
|
||||
viewThemeUtils,
|
||||
processedMessageText,
|
||||
message,
|
||||
binding.incomingPreviewMessageBubble);
|
||||
}
|
||||
binding.incomingPreviewMessageBubble.setOnClickListener(null);
|
||||
|
||||
float textSize = 0;
|
||||
if (context != null) {
|
||||
textSize = context.getResources().getDimension(R.dimen.chat_text_size);
|
||||
}
|
||||
HashMap<String, HashMap<String, String>> messageParameters = message.getMessageParameters();
|
||||
if (
|
||||
(messageParameters == null || messageParameters.size() <= 0) &&
|
||||
TextMatchers.isMessageWithSingleEmoticonOnly(message.getText())
|
||||
) {
|
||||
textSize = (float) (textSize * IncomingTextMessageViewHolder.TEXT_SIZE_MULTIPLIER);
|
||||
itemView.setSelected(true);
|
||||
}
|
||||
binding.messageCaption.setVisibility(View.VISIBLE);
|
||||
binding.messageCaption.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
|
||||
binding.messageCaption.setText(processedMessageText);
|
||||
} else {
|
||||
binding.incomingPreviewMessageBubble.setBackground(null);
|
||||
binding.messageCaption.setVisibility(View.GONE);
|
||||
}
|
||||
binding.messageAuthor.setText(message.getActorDisplayName());
|
||||
binding.messageText.setTextColor(ContextCompat.getColor(binding.messageText.getContext(),
|
||||
R.color.no_emphasis_text));
|
||||
@ -63,6 +111,12 @@ public class IncomingPreviewMessageViewHolder extends PreviewMessageViewHolder {
|
||||
return binding.messageText;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public EmojiTextView getMessageCaption() {
|
||||
return binding.messageCaption;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgressBar getProgressBar() {
|
||||
return binding.progressBar;
|
||||
@ -99,5 +153,4 @@ public class IncomingPreviewMessageViewHolder extends PreviewMessageViewHolder {
|
||||
|
||||
@Override
|
||||
public ReactionsInsideMessageBinding getReactionsBinding(){ return binding.reactions; }
|
||||
|
||||
}
|
||||
|
@ -89,6 +89,15 @@ class IncomingVoiceMessageViewHolder(incomingView: View, payload: Any) :
|
||||
this.message = message
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
val filename = message.selectedIndividualHashMap!!["name"]
|
||||
val retrieved = appPreferences!!.getWaveFormFromFile(filename)
|
||||
if (retrieved.isNotEmpty() &&
|
||||
message.voiceMessageFloatArray == null ||
|
||||
message.voiceMessageFloatArray?.isEmpty() == true
|
||||
) {
|
||||
message.voiceMessageFloatArray = retrieved.toFloatArray()
|
||||
binding.seekbar.setWaveData(message.voiceMessageFloatArray!!)
|
||||
}
|
||||
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
|
||||
|
||||
setAvatarAndAuthorOnMessageItem(message)
|
||||
|
@ -38,12 +38,7 @@ import io.reactivex.schedulers.Schedulers
|
||||
|
||||
class LinkPreview {
|
||||
|
||||
fun showLink(
|
||||
message: ChatMessage,
|
||||
ncApi: NcApi,
|
||||
binding: ReferenceInsideMessageBinding,
|
||||
context: Context
|
||||
) {
|
||||
fun showLink(message: ChatMessage, ncApi: NcApi, binding: ReferenceInsideMessageBinding, context: Context) {
|
||||
binding.referenceName.text = ""
|
||||
binding.referenceDescription.text = ""
|
||||
binding.referenceLink.text = ""
|
||||
|
@ -209,6 +209,6 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) :
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = NextcloudTalkApplication::class.java.simpleName
|
||||
private val TAG = OutcomingPollMessageViewHolder::class.java.simpleName
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,8 @@
|
||||
|
||||
package com.nextcloud.talk.adapters.messages;
|
||||
|
||||
import android.text.Spanned;
|
||||
import android.util.TypedValue;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
@ -31,7 +33,12 @@ import com.nextcloud.talk.R;
|
||||
import com.nextcloud.talk.databinding.ItemCustomOutcomingPreviewMessageBinding;
|
||||
import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding;
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage;
|
||||
import com.nextcloud.talk.utils.TextMatchers;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Objects;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.emoji2.widget.EmojiTextView;
|
||||
|
||||
@ -45,8 +52,51 @@ public class OutcomingPreviewMessageViewHolder extends PreviewMessageViewHolder
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBind(ChatMessage message) {
|
||||
public void onBind(@NonNull ChatMessage message) {
|
||||
super.onBind(message);
|
||||
if(!message.isVoiceMessage()
|
||||
&& !Objects.equals(message.getMessage(), "{file}")
|
||||
) {
|
||||
Spanned processedMessageText = null;
|
||||
binding.outgoingPreviewMessageBubble.setBackgroundResource(R.drawable.shape_grouped_outcoming_message);
|
||||
if (viewThemeUtils != null) {
|
||||
processedMessageText = messageUtils.enrichChatMessageText(
|
||||
binding.messageCaption.getContext(),
|
||||
message,
|
||||
false,
|
||||
viewThemeUtils);
|
||||
viewThemeUtils.talk.themeOutgoingMessageBubble(binding.outgoingPreviewMessageBubble, true, false);
|
||||
}
|
||||
|
||||
if (processedMessageText != null) {
|
||||
processedMessageText = messageUtils.processMessageParameters(
|
||||
binding.messageCaption.getContext(),
|
||||
viewThemeUtils,
|
||||
processedMessageText,
|
||||
message,
|
||||
binding.outgoingPreviewMessageBubble);
|
||||
}
|
||||
binding.outgoingPreviewMessageBubble.setOnClickListener(null);
|
||||
|
||||
float textSize = 0;
|
||||
if (context != null) {
|
||||
textSize = context.getResources().getDimension(R.dimen.chat_text_size);
|
||||
}
|
||||
HashMap<String, HashMap<String, String>> messageParameters = message.getMessageParameters();
|
||||
if (
|
||||
(messageParameters == null || messageParameters.size() <= 0) &&
|
||||
TextMatchers.isMessageWithSingleEmoticonOnly(message.getText())
|
||||
) {
|
||||
textSize = (float)(textSize * IncomingTextMessageViewHolder.TEXT_SIZE_MULTIPLIER);
|
||||
itemView.setSelected(true);
|
||||
}
|
||||
binding.messageCaption.setVisibility(View.VISIBLE);
|
||||
binding.messageCaption.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
|
||||
binding.messageCaption.setText(processedMessageText);
|
||||
} else {
|
||||
binding.outgoingPreviewMessageBubble.setBackground(null);
|
||||
binding.messageCaption.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
binding.messageText.setTextColor(ContextCompat.getColor(binding.messageText.getContext(),
|
||||
R.color.no_emphasis_text));
|
||||
@ -54,6 +104,7 @@ public class OutcomingPreviewMessageViewHolder extends PreviewMessageViewHolder
|
||||
R.color.no_emphasis_text));
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public EmojiTextView getMessageText() {
|
||||
return binding.messageText;
|
||||
@ -64,21 +115,25 @@ public class OutcomingPreviewMessageViewHolder extends PreviewMessageViewHolder
|
||||
return binding.progressBar;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public View getPreviewContainer() {
|
||||
return binding.previewContainer;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public MaterialCardView getPreviewContactContainer() {
|
||||
return binding.contactContainer;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ImageView getPreviewContactPhoto() {
|
||||
return binding.contactPhoto;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public EmojiTextView getPreviewContactName() {
|
||||
return binding.contactName;
|
||||
@ -91,4 +146,8 @@ public class OutcomingPreviewMessageViewHolder extends PreviewMessageViewHolder
|
||||
|
||||
@Override
|
||||
public ReactionsInsideMessageBinding getReactionsBinding() { return binding.reactions; }
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public EmojiTextView getMessageCaption() { return binding.messageCaption; }
|
||||
}
|
||||
|
@ -86,6 +86,17 @@ class OutcomingVoiceMessageViewHolder(outcomingView: View) :
|
||||
this.message = message
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
viewThemeUtils.platform.colorTextView(binding.messageTime, ColorRole.ON_SURFACE_VARIANT)
|
||||
|
||||
val filename = message.selectedIndividualHashMap!!["name"]
|
||||
val retrieved = appPreferences!!.getWaveFormFromFile(filename)
|
||||
if (retrieved.isNotEmpty() &&
|
||||
message.voiceMessageFloatArray == null ||
|
||||
message.voiceMessageFloatArray?.isEmpty() == true
|
||||
) {
|
||||
message.voiceMessageFloatArray = retrieved.toFloatArray()
|
||||
binding.seekbar.setWaveData(message.voiceMessageFloatArray!!)
|
||||
}
|
||||
|
||||
binding.messageTime.text = dateUtils.getLocalTimeStringFromTimestamp(message.timestamp)
|
||||
|
||||
colorizeMessageBubble(message)
|
||||
|
@ -51,11 +51,13 @@ import com.nextcloud.talk.databinding.ReactionsInsideMessageBinding
|
||||
import com.nextcloud.talk.extensions.loadChangelogBotAvatar
|
||||
import com.nextcloud.talk.models.json.chat.ChatMessage
|
||||
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
import com.nextcloud.talk.utils.DateUtils
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.nextcloud.talk.utils.DrawableUtils.getDrawableResourceIdForMimeType
|
||||
import com.nextcloud.talk.utils.FileViewerUtils
|
||||
import com.nextcloud.talk.utils.FileViewerUtils.ProgressUi
|
||||
import com.nextcloud.talk.utils.message.MessageUtils
|
||||
import com.stfalcon.chatkit.messages.MessageHolders.IncomingImageMessageViewHolder
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.SingleObserver
|
||||
@ -80,6 +82,12 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
|
||||
@Inject
|
||||
lateinit var dateUtils: DateUtils
|
||||
|
||||
@Inject
|
||||
lateinit var messageUtils: MessageUtils
|
||||
|
||||
@Inject
|
||||
lateinit var userManager: UserManager
|
||||
|
||||
@JvmField
|
||||
@Inject
|
||||
var okHttpClient: OkHttpClient? = null
|
||||
@ -111,6 +119,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
|
||||
if (message.getCalculateMessageType() === ChatMessage.MessageType.SINGLE_NC_ATTACHMENT_MESSAGE) {
|
||||
fileViewerUtils = FileViewerUtils(context!!, message.activeUser!!)
|
||||
val fileName = message.selectedIndividualHashMap!![KEY_NAME]
|
||||
|
||||
messageText.text = fileName
|
||||
|
||||
if (message.activeUser != null &&
|
||||
@ -123,7 +132,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
|
||||
ProgressUi(progressBar, messageText, image)
|
||||
)
|
||||
}
|
||||
clickView!!.setOnLongClickListener { l: View? ->
|
||||
clickView!!.setOnLongClickListener {
|
||||
previewMessageInterface!!.onPreviewMessageLongClick(message)
|
||||
true
|
||||
}
|
||||
@ -188,6 +197,12 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
messageCaption.setOnClickListener(null)
|
||||
messageCaption.setOnLongClickListener {
|
||||
previewMessageInterface!!.onPreviewMessageLongClick(message)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun longClickOnReaction(chatMessage: ChatMessage) {
|
||||
@ -312,6 +327,7 @@ abstract class PreviewMessageViewHolder(itemView: View?, payload: Any?) :
|
||||
}
|
||||
|
||||
abstract val messageText: EmojiTextView
|
||||
abstract val messageCaption: EmojiTextView
|
||||
abstract val previewContainer: View
|
||||
abstract val previewContactContainer: MaterialCardView
|
||||
abstract val previewContactPhoto: ImageView
|
||||
|
@ -49,6 +49,7 @@ import com.nextcloud.talk.models.json.unifiedsearch.UnifiedSearchOverall;
|
||||
import com.nextcloud.talk.models.json.userprofile.UserProfileFieldsOverall;
|
||||
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;
|
||||
import com.nextcloud.talk.polls.repositories.model.PollOverall;
|
||||
import com.nextcloud.talk.translate.repositories.model.LanguagesOverall;
|
||||
import com.nextcloud.talk.translate.repositories.model.TranslationsOverall;
|
||||
|
||||
import java.util.List;
|
||||
@ -56,6 +57,7 @@ import java.util.Map;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import io.reactivex.Observable;
|
||||
import kotlin.Unit;
|
||||
import okhttp3.MultipartBody;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.ResponseBody;
|
||||
@ -332,7 +334,7 @@ public interface NcApi {
|
||||
|
||||
@FormUrlEncoded
|
||||
@POST
|
||||
Observable<Void> registerDeviceForNotificationsWithPushProxy(@Url String url,
|
||||
Observable<Unit> registerDeviceForNotificationsWithPushProxy(@Url String url,
|
||||
@FieldMap Map<String, String> fields);
|
||||
|
||||
|
||||
@ -675,6 +677,10 @@ public interface NcApi {
|
||||
@Query("toLanguage") String toLanguage,
|
||||
@Nullable @Query("fromLanguage") String fromLanguage);
|
||||
|
||||
@GET
|
||||
Observable<LanguagesOverall> getLanguages(@Header("Authorization") String authorization,
|
||||
@Url String url);
|
||||
|
||||
@GET
|
||||
Observable<ReminderOverall> getReminder(@Header("Authorization") String authorization,
|
||||
@Url String url);
|
||||
|
@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.controllers.bottomsheet.items
|
||||
package com.nextcloud.talk.bottomsheet.items
|
||||
|
||||
import android.widget.ImageView
|
||||
import androidx.annotation.DrawableRes
|
@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.controllers.bottomsheet.items
|
||||
package com.nextcloud.talk.bottomsheet.items
|
||||
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
@ -18,7 +18,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.controllers.bottomsheet.items
|
||||
package com.nextcloud.talk.bottomsheet.items
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -79,10 +79,7 @@ internal class ListIconDialogAdapter<IT : ListItemWithImage>(
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): ListItemViewHolder {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListItemViewHolder {
|
||||
val listItemView: View = parent.inflate(dialog.windowContext, R.layout.menu_item_sheet)
|
||||
val viewHolder = ListItemViewHolder(
|
||||
itemView = listItemView,
|
||||
@ -94,10 +91,7 @@ internal class ListIconDialogAdapter<IT : ListItemWithImage>(
|
||||
|
||||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onBindViewHolder(
|
||||
holder: ListItemViewHolder,
|
||||
position: Int
|
||||
) {
|
||||
override fun onBindViewHolder(holder: ListItemViewHolder, position: Int) {
|
||||
holder.itemView.isEnabled = !disabledIndices.contains(position)
|
||||
val currentItem = items[position]
|
||||
|
||||
@ -120,10 +114,7 @@ internal class ListIconDialogAdapter<IT : ListItemWithImage>(
|
||||
}
|
||||
}
|
||||
|
||||
override fun replaceItems(
|
||||
items: List<IT>,
|
||||
listener: ListItemListener<IT>
|
||||
) {
|
||||
override fun replaceItems(items: List<IT>, listener: ListItemListener<IT>) {
|
||||
this.items = items
|
||||
if (listener != null) {
|
||||
this.selection = listener
|
@ -46,10 +46,7 @@ class ReactionAnimator(
|
||||
) {
|
||||
private val reactionsList: MutableList<CallReaction> = ArrayList()
|
||||
|
||||
fun addReaction(
|
||||
emoji: String,
|
||||
displayName: String
|
||||
) {
|
||||
fun addReaction(emoji: String, displayName: String) {
|
||||
val callReaction = CallReaction(emoji, displayName)
|
||||
reactionsList.add(callReaction)
|
||||
|
||||
@ -58,9 +55,7 @@ class ReactionAnimator(
|
||||
}
|
||||
}
|
||||
|
||||
private fun animateReaction(
|
||||
callReaction: CallReaction
|
||||
) {
|
||||
private fun animateReaction(callReaction: CallReaction) {
|
||||
val reactionWrapper = getReactionWrapperView(callReaction)
|
||||
val params = RelativeLayout.LayoutParams(
|
||||
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||
|
@ -317,7 +317,7 @@ class CallNotificationActivity : CallBaseActivity() {
|
||||
}
|
||||
|
||||
override fun suppressFitsSystemWindows() {
|
||||
binding!!.controllerCallNotificationLayout.fitsSystemWindows = false
|
||||
binding!!.callNotificationLayout.fitsSystemWindows = false
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -34,7 +34,6 @@ import android.annotation.SuppressLint
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.AssetFileDescriptor
|
||||
@ -86,7 +85,6 @@ import android.widget.RelativeLayout.LayoutParams
|
||||
import android.widget.SeekBar
|
||||
import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
@ -116,7 +114,6 @@ import coil.target.Target
|
||||
import coil.transform.CircleCropTransformation
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.nextcloud.android.common.ui.theme.utils.ColorRole
|
||||
import com.nextcloud.talk.BuildConfig
|
||||
@ -159,7 +156,6 @@ import com.nextcloud.talk.events.UserMentionClickEvent
|
||||
import com.nextcloud.talk.events.WebSocketCommunicationEvent
|
||||
import com.nextcloud.talk.extensions.loadAvatarOrImagePreview
|
||||
import com.nextcloud.talk.jobs.DownloadFileToCacheWorker
|
||||
import com.nextcloud.talk.jobs.SaveFileToStorageWorker
|
||||
import com.nextcloud.talk.jobs.ShareOperationWorker
|
||||
import com.nextcloud.talk.jobs.UploadAndShareFilesWorker
|
||||
import com.nextcloud.talk.location.LocationPickerActivity
|
||||
@ -192,7 +188,9 @@ import com.nextcloud.talk.ui.StatusDrawable
|
||||
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
|
||||
import com.nextcloud.talk.ui.dialog.AttachmentDialog
|
||||
import com.nextcloud.talk.ui.dialog.DateTimePickerFragment
|
||||
import com.nextcloud.talk.ui.dialog.FileAttachmentPreviewFragment
|
||||
import com.nextcloud.talk.ui.dialog.MessageActionsDialog
|
||||
import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment
|
||||
import com.nextcloud.talk.ui.dialog.ShowReactionsDialog
|
||||
import com.nextcloud.talk.ui.recyclerview.MessageSwipeActions
|
||||
import com.nextcloud.talk.ui.recyclerview.MessageSwipeCallback
|
||||
@ -261,6 +259,8 @@ import java.util.Objects
|
||||
import java.util.concurrent.ExecutionException
|
||||
import javax.inject.Inject
|
||||
import kotlin.collections.set
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.log10
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
@ -342,17 +342,32 @@ class ChatActivity :
|
||||
|
||||
private val filesToUpload: MutableList<String> = ArrayList()
|
||||
private lateinit var sharedText: String
|
||||
var isVoiceRecordingInProgress: Boolean = false
|
||||
var currentVoiceRecordFile: String = ""
|
||||
var isVoiceRecordingLocked: Boolean = false
|
||||
private var isVoicePreviewPlaying: Boolean = false
|
||||
|
||||
private var recorder: MediaRecorder? = null
|
||||
private enum class MediaRecorderState {
|
||||
INITIAL,
|
||||
INITIALIZED,
|
||||
CONFIGURED,
|
||||
PREPARED,
|
||||
RECORDING,
|
||||
RELEASED,
|
||||
ERROR
|
||||
}
|
||||
private var mediaRecorderState: MediaRecorderState = MediaRecorderState.INITIAL
|
||||
|
||||
private var voicePreviewMediaPlayer: MediaPlayer? = null
|
||||
private var voicePreviewObjectAnimator: ObjectAnimator? = null
|
||||
|
||||
var mediaPlayer: MediaPlayer? = null
|
||||
lateinit var mediaPlayerHandler: Handler
|
||||
|
||||
private var isEmojiPickerVisible = false
|
||||
|
||||
private var currentlyPlayedVoiceMessage: ChatMessage? = null
|
||||
|
||||
private lateinit var micInputAudioRecorder: AudioRecord
|
||||
private var micInputAudioRecordThread: Thread? = null
|
||||
private var isMicInputAudioThreadRunning: Boolean = false
|
||||
@ -361,7 +376,9 @@ class ChatActivity :
|
||||
AudioFormat.CHANNEL_IN_MONO,
|
||||
AudioFormat.ENCODING_PCM_16BIT
|
||||
)
|
||||
|
||||
private var voiceRecordDuration = 0L
|
||||
private var voiceRecordPauseTime = 0L
|
||||
|
||||
// messy workaround for a mediaPlayer bug, don't delete
|
||||
private var lastRecordMediaPosition: Int = 0
|
||||
@ -519,7 +536,7 @@ class ChatActivity :
|
||||
if (isMicInputAudioThreadRunning) {
|
||||
stopMicInputRecordingAnimation()
|
||||
}
|
||||
if (isVoiceRecordingInProgress) {
|
||||
if (mediaRecorderState == MediaRecorderState.RECORDING) {
|
||||
stopAudioRecording()
|
||||
}
|
||||
if (currentlyPlayedVoiceMessage != null) {
|
||||
@ -894,7 +911,12 @@ class ChatActivity :
|
||||
if (message.isPlayingVoiceMessage) {
|
||||
pausePlayback(message)
|
||||
} else {
|
||||
setUpWaveform(message)
|
||||
val retrieved = appPreferences.getWaveFormFromFile(filename)
|
||||
if (retrieved.isEmpty()) {
|
||||
setUpWaveform(message)
|
||||
} else {
|
||||
startPlayback(message)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Downloaded to cache")
|
||||
@ -913,6 +935,7 @@ class ChatActivity :
|
||||
adapter?.update(message)
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val r = AudioUtils.audioFileToFloatArray(file)
|
||||
appPreferences.saveWaveFormForFile(filename, r.toTypedArray())
|
||||
message.voiceMessageFloatArray = r
|
||||
withContext(Dispatchers.Main) {
|
||||
startPlayback(message)
|
||||
@ -925,7 +948,7 @@ class ChatActivity :
|
||||
|
||||
private fun initMessageHolders(): MessageHolders {
|
||||
val messageHolders = MessageHolders()
|
||||
val profileBottomSheet = ProfileBottomSheet(ncApi, conversationUser!!)
|
||||
val profileBottomSheet = ProfileBottomSheet(ncApi, conversationUser!!, viewThemeUtils)
|
||||
|
||||
val payload = MessagePayload(
|
||||
roomToken,
|
||||
@ -1037,10 +1060,13 @@ class ChatActivity :
|
||||
} else {
|
||||
showMicrophoneButton(true)
|
||||
}
|
||||
} else if (isVoiceRecordingInProgress) {
|
||||
} else if (mediaRecorderState == MediaRecorderState.RECORDING) {
|
||||
binding.messageInputView.playPauseBtn.visibility = View.GONE
|
||||
binding.messageInputView.seekBar.visibility = View.GONE
|
||||
} else {
|
||||
showVoiceRecordingLockedInterface(true)
|
||||
showPreviewVoiceRecording(true)
|
||||
stopMicInputRecordingAnimation()
|
||||
binding.messageInputView.micInputCloud.setState(MicInputCloud.ViewState.PAUSED_STATE)
|
||||
}
|
||||
|
||||
@ -1061,57 +1087,22 @@ class ChatActivity :
|
||||
|
||||
var voiceRecordStartTime = 0L
|
||||
var voiceRecordEndTime = 0L
|
||||
var voiceRecordPauseTime = 0L
|
||||
val micInputCloudLayoutParams: LayoutParams = binding.messageInputView.micInputCloud
|
||||
.layoutParams as LayoutParams
|
||||
|
||||
val deleteVoiceRecordingLayoutParams: LayoutParams = binding.messageInputView.deleteVoiceRecording
|
||||
.layoutParams as LayoutParams
|
||||
|
||||
val sendVoiceRecordingLayoutParams: LayoutParams = binding.messageInputView.sendVoiceRecording
|
||||
.layoutParams as LayoutParams
|
||||
|
||||
// this is so that the seekbar is no longer draggable
|
||||
binding.messageInputView.seekBar.setOnTouchListener(OnTouchListener { _, _ -> true })
|
||||
|
||||
binding.messageInputView.micInputCloud.setOnClickListener {
|
||||
if (isVoiceRecordingInProgress) {
|
||||
if (mediaRecorderState == MediaRecorderState.RECORDING) {
|
||||
recorder?.stop()
|
||||
mediaRecorderState = MediaRecorderState.INITIAL
|
||||
stopMicInputRecordingAnimation()
|
||||
voiceRecordPauseTime = binding.messageInputView.audioRecordDuration.base - SystemClock.elapsedRealtime()
|
||||
binding.messageInputView.audioRecordDuration.stop()
|
||||
binding.messageInputView.audioRecordDuration.visibility = View.GONE
|
||||
binding.messageInputView.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.messageInputView.playPauseBtn.icon = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.ic_baseline_play_arrow_voice_message_24
|
||||
)
|
||||
binding.messageInputView.seekBar.visibility = View.VISIBLE
|
||||
binding.messageInputView.seekBar.progress = 0
|
||||
binding.messageInputView.seekBar.max = 0
|
||||
micInputCloudLayoutParams.removeRule(BELOW)
|
||||
micInputCloudLayoutParams.addRule(BELOW, R.id.voice_preview_container)
|
||||
deleteVoiceRecordingLayoutParams.removeRule(BELOW)
|
||||
deleteVoiceRecordingLayoutParams.addRule(BELOW, R.id.voice_preview_container)
|
||||
sendVoiceRecordingLayoutParams.removeRule(BELOW)
|
||||
sendVoiceRecordingLayoutParams.addRule(BELOW, R.id.voice_preview_container)
|
||||
showPreviewVoiceRecording(true)
|
||||
} else {
|
||||
restartAudio()
|
||||
stopPreviewVoicePlaying()
|
||||
initMediaRecorder(currentVoiceRecordFile)
|
||||
startMicInputRecordingAnimation()
|
||||
binding.messageInputView.audioRecordDuration.base = SystemClock.elapsedRealtime()
|
||||
binding.messageInputView.audioRecordDuration.start()
|
||||
binding.messageInputView.playPauseBtn.visibility = View.GONE
|
||||
binding.messageInputView.seekBar.visibility = View.GONE
|
||||
binding.messageInputView.audioRecordDuration.visibility = View.VISIBLE
|
||||
micInputCloudLayoutParams.removeRule(BELOW)
|
||||
micInputCloudLayoutParams.addRule(BELOW, R.id.audioRecordDuration)
|
||||
deleteVoiceRecordingLayoutParams.removeRule(BELOW)
|
||||
deleteVoiceRecordingLayoutParams.addRule(BELOW, R.id.audioRecordDuration)
|
||||
sendVoiceRecordingLayoutParams.removeRule(BELOW)
|
||||
sendVoiceRecordingLayoutParams.addRule(BELOW, R.id.audioRecordDuration)
|
||||
showPreviewVoiceRecording(false)
|
||||
}
|
||||
|
||||
isVoiceRecordingInProgress = !isVoiceRecordingInProgress
|
||||
}
|
||||
|
||||
binding.messageInputView.deleteVoiceRecording.setOnClickListener {
|
||||
@ -1176,7 +1167,7 @@ class ChatActivity :
|
||||
|
||||
MotionEvent.ACTION_CANCEL -> {
|
||||
Log.d(TAG, "ACTION_CANCEL. same as for UP")
|
||||
if (!isVoiceRecordingInProgress || !isRecordAudioPermissionGranted()) {
|
||||
if (mediaRecorderState != MediaRecorderState.RECORDING || !isRecordAudioPermissionGranted()) {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1187,7 +1178,7 @@ class ChatActivity :
|
||||
|
||||
MotionEvent.ACTION_UP -> {
|
||||
Log.d(TAG, "ACTION_UP. stop recording??")
|
||||
if (!isVoiceRecordingInProgress ||
|
||||
if (mediaRecorderState != MediaRecorderState.RECORDING ||
|
||||
!isRecordAudioPermissionGranted() ||
|
||||
isVoiceRecordingLocked
|
||||
) {
|
||||
@ -1218,7 +1209,7 @@ class ChatActivity :
|
||||
MotionEvent.ACTION_MOVE -> {
|
||||
Log.d(TAG, "ACTION_MOVE.")
|
||||
|
||||
if (!isVoiceRecordingInProgress || !isRecordAudioPermissionGranted()) {
|
||||
if (mediaRecorderState != MediaRecorderState.RECORDING || !isRecordAudioPermissionGranted()) {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -1235,6 +1226,7 @@ class ChatActivity :
|
||||
isVoiceRecordingLocked = true
|
||||
showVoiceRecordingLocked(true)
|
||||
showVoiceRecordingLockedInterface(true)
|
||||
startMicInputRecordingAnimation()
|
||||
} else if (deltaY < 0f) {
|
||||
binding.voiceRecordingLock.translationY = deltaY
|
||||
}
|
||||
@ -1271,9 +1263,51 @@ class ChatActivity :
|
||||
})
|
||||
}
|
||||
|
||||
private fun showPreviewVoiceRecording(value: Boolean) {
|
||||
val micInputCloudLayoutParams: LayoutParams = binding.messageInputView.micInputCloud
|
||||
.layoutParams as LayoutParams
|
||||
|
||||
val deleteVoiceRecordingLayoutParams: LayoutParams = binding.messageInputView.deleteVoiceRecording
|
||||
.layoutParams as LayoutParams
|
||||
|
||||
val sendVoiceRecordingLayoutParams: LayoutParams = binding.messageInputView.sendVoiceRecording
|
||||
.layoutParams as LayoutParams
|
||||
|
||||
if (value) {
|
||||
voiceRecordPauseTime = binding.messageInputView.audioRecordDuration.base - SystemClock.elapsedRealtime()
|
||||
binding.messageInputView.audioRecordDuration.stop()
|
||||
binding.messageInputView.audioRecordDuration.visibility = View.GONE
|
||||
binding.messageInputView.playPauseBtn.visibility = View.VISIBLE
|
||||
binding.messageInputView.playPauseBtn.icon = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.ic_baseline_play_arrow_voice_message_24
|
||||
)
|
||||
binding.messageInputView.seekBar.visibility = View.VISIBLE
|
||||
binding.messageInputView.seekBar.progress = 0
|
||||
binding.messageInputView.seekBar.max = 0
|
||||
micInputCloudLayoutParams.removeRule(BELOW)
|
||||
micInputCloudLayoutParams.addRule(BELOW, R.id.voice_preview_container)
|
||||
deleteVoiceRecordingLayoutParams.removeRule(BELOW)
|
||||
deleteVoiceRecordingLayoutParams.addRule(BELOW, R.id.voice_preview_container)
|
||||
sendVoiceRecordingLayoutParams.removeRule(BELOW)
|
||||
sendVoiceRecordingLayoutParams.addRule(BELOW, R.id.voice_preview_container)
|
||||
} else {
|
||||
binding.messageInputView.audioRecordDuration.base = SystemClock.elapsedRealtime()
|
||||
binding.messageInputView.audioRecordDuration.start()
|
||||
binding.messageInputView.playPauseBtn.visibility = View.GONE
|
||||
binding.messageInputView.seekBar.visibility = View.GONE
|
||||
binding.messageInputView.audioRecordDuration.visibility = View.VISIBLE
|
||||
micInputCloudLayoutParams.removeRule(BELOW)
|
||||
micInputCloudLayoutParams.addRule(BELOW, R.id.audioRecordDuration)
|
||||
deleteVoiceRecordingLayoutParams.removeRule(BELOW)
|
||||
deleteVoiceRecordingLayoutParams.addRule(BELOW, R.id.audioRecordDuration)
|
||||
sendVoiceRecordingLayoutParams.removeRule(BELOW)
|
||||
sendVoiceRecordingLayoutParams.addRule(BELOW, R.id.audioRecordDuration)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initPreviewVoiceRecording() {
|
||||
voicePreviewMediaPlayer = MediaPlayer().apply {
|
||||
Log.e(TAG, currentVoiceRecordFile)
|
||||
setDataSource(currentVoiceRecordFile)
|
||||
prepare()
|
||||
setOnPreparedListener {
|
||||
@ -1327,20 +1361,6 @@ class ChatActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private fun restartAudio() {
|
||||
recorder = MediaRecorder().apply {
|
||||
setAudioSource(MediaRecorder.AudioSource.MIC)
|
||||
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
|
||||
setOutputFile(currentVoiceRecordFile)
|
||||
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
|
||||
setAudioSamplingRate(VOICE_MESSAGE_SAMPLING_RATE)
|
||||
setAudioEncodingBitRate(VOICE_MESSAGE_ENCODING_BIT_RATE)
|
||||
setAudioChannels(VOICE_MESSAGE_CHANNELS)
|
||||
prepare()
|
||||
start()
|
||||
}
|
||||
}
|
||||
|
||||
private fun endVoiceRecordingUI() {
|
||||
stopPreviewVoicePlaying()
|
||||
showRecordAudioUi(false)
|
||||
@ -1348,6 +1368,7 @@ class ChatActivity :
|
||||
isVoiceRecordingLocked = false
|
||||
showVoiceRecordingLocked(false)
|
||||
showVoiceRecordingLockedInterface(false)
|
||||
stopMicInputRecordingAnimation()
|
||||
}
|
||||
|
||||
private fun showVoiceRecordingLocked(value: Boolean) {
|
||||
@ -1408,10 +1429,7 @@ class ChatActivity :
|
||||
audioDurationLayoutParams.removeRule(RelativeLayout.END_OF)
|
||||
audioDurationLayoutParams.addRule(RelativeLayout.CENTER_HORIZONTAL, R.bool.value_true)
|
||||
audioDurationLayoutParams.setMargins(0, standardQuarterMargin, 0, 0)
|
||||
startMicInputRecordingAnimation()
|
||||
Log.d(TAG, "MicInputRecording Started")
|
||||
} else {
|
||||
stopMicInputRecordingAnimation()
|
||||
binding.messageInputView.deleteVoiceRecording.visibility = View.GONE
|
||||
binding.messageInputView.micInputCloud.visibility = View.GONE
|
||||
binding.messageInputView.recordAudioButton.visibility = View.VISIBLE
|
||||
@ -1615,7 +1633,7 @@ class ChatActivity :
|
||||
participantPermissions.hasChatPermission() &&
|
||||
!isReadOnlyConversation()
|
||||
) {
|
||||
val messageSwipeController = MessageSwipeCallback(
|
||||
val messageSwipeCallback = MessageSwipeCallback(
|
||||
this,
|
||||
object : MessageSwipeActions {
|
||||
override fun showReplyUI(position: Int) {
|
||||
@ -1627,7 +1645,7 @@ class ChatActivity :
|
||||
}
|
||||
)
|
||||
|
||||
val itemTouchHelper = ItemTouchHelper(messageSwipeController)
|
||||
val itemTouchHelper = ItemTouchHelper(messageSwipeCallback)
|
||||
itemTouchHelper.attachToRecyclerView(binding.messagesListView)
|
||||
}
|
||||
}
|
||||
@ -1705,14 +1723,17 @@ class ChatActivity :
|
||||
}
|
||||
}
|
||||
|
||||
fun isOneToOneConversation() = currentConversation != null && currentConversation?.type != null &&
|
||||
currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
|
||||
fun isOneToOneConversation() =
|
||||
currentConversation != null && currentConversation?.type != null &&
|
||||
currentConversation?.type == ConversationType.ROOM_TYPE_ONE_TO_ONE_CALL
|
||||
|
||||
private fun isGroupConversation() = currentConversation != null && currentConversation?.type != null &&
|
||||
currentConversation?.type == ConversationType.ROOM_GROUP_CALL
|
||||
private fun isGroupConversation() =
|
||||
currentConversation != null && currentConversation?.type != null &&
|
||||
currentConversation?.type == ConversationType.ROOM_GROUP_CALL
|
||||
|
||||
private fun isPublicConversation() = currentConversation != null && currentConversation?.type != null &&
|
||||
currentConversation?.type == ConversationType.ROOM_PUBLIC_CALL
|
||||
private fun isPublicConversation() =
|
||||
currentConversation != null && currentConversation?.type != null &&
|
||||
currentConversation?.type == ConversationType.ROOM_PUBLIC_CALL
|
||||
|
||||
private fun switchToRoom(token: String, startCallAfterRoomSwitch: Boolean, isVoiceOnlyCall: Boolean) {
|
||||
if (conversationUser != null) {
|
||||
@ -2028,44 +2049,6 @@ class ChatActivity :
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
private fun saveImageToStorage(
|
||||
message: ChatMessage
|
||||
) {
|
||||
message.openWhenDownloaded = false
|
||||
adapter?.update(message)
|
||||
|
||||
val fileName = message.selectedIndividualHashMap!!["name"]
|
||||
val sourceFilePath = applicationContext.cacheDir.path
|
||||
val fileId = message.selectedIndividualHashMap!!["id"]
|
||||
|
||||
val workers = WorkManager.getInstance(context).getWorkInfosByTag(fileId!!)
|
||||
try {
|
||||
for (workInfo in workers.get()) {
|
||||
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||
Log.d(TAG, "SaveFileToStorageWorker for $fileId is already running or scheduled")
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e: ExecutionException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
} catch (e: InterruptedException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
}
|
||||
|
||||
val data: Data = Data.Builder()
|
||||
.putString(SaveFileToStorageWorker.KEY_FILE_NAME, fileName)
|
||||
.putString(SaveFileToStorageWorker.KEY_SOURCE_FILE_PATH, "$sourceFilePath/$fileName")
|
||||
.build()
|
||||
|
||||
val saveWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder(SaveFileToStorageWorker::class.java)
|
||||
.setInputData(data)
|
||||
.addTag(fileId)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance().enqueue(saveWorker)
|
||||
}
|
||||
|
||||
@SuppressLint("SimpleDateFormat")
|
||||
private fun setVoiceRecordFileName() {
|
||||
val simpleDateFormat = SimpleDateFormat(FILE_DATE_PATTERN)
|
||||
@ -2123,36 +2106,41 @@ class ChatActivity :
|
||||
)
|
||||
isMicInputAudioThreadRunning = true
|
||||
micInputAudioRecorder.startRecording()
|
||||
micInputAudioRecordThread = Thread(
|
||||
Runnable {
|
||||
while (isMicInputAudioThreadRunning) {
|
||||
val byteArr = ByteArray(bufferSize / 2)
|
||||
micInputAudioRecorder.read(byteArr, 0, byteArr.size)
|
||||
val d = Math.abs(byteArr[0].toDouble())
|
||||
if (d > AUDIO_VALUE_MAX) {
|
||||
binding.messageInputView.micInputCloud.setRotationSpeed(
|
||||
Math.log10(d).toFloat(),
|
||||
MicInputCloud.MAXIMUM_RADIUS
|
||||
)
|
||||
} else if (d > AUDIO_VALUE_MIN) {
|
||||
binding.messageInputView.micInputCloud.setRotationSpeed(
|
||||
Math.log10(d).toFloat(),
|
||||
MicInputCloud.EXTENDED_RADIUS
|
||||
)
|
||||
} else {
|
||||
binding.messageInputView.micInputCloud.setRotationSpeed(
|
||||
1f,
|
||||
MicInputCloud.DEFAULT_RADIUS
|
||||
)
|
||||
}
|
||||
Thread.sleep(AUDIO_VALUE_SLEEP)
|
||||
}
|
||||
}
|
||||
)
|
||||
initMicInputAudioRecordThread()
|
||||
micInputAudioRecordThread!!.start()
|
||||
binding.messageInputView.micInputCloud.startAnimators()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initMicInputAudioRecordThread() {
|
||||
micInputAudioRecordThread = Thread(
|
||||
Runnable {
|
||||
while (isMicInputAudioThreadRunning) {
|
||||
val byteArr = ByteArray(bufferSize / 2)
|
||||
micInputAudioRecorder.read(byteArr, 0, byteArr.size)
|
||||
val d = abs(byteArr[0].toDouble())
|
||||
if (d > AUDIO_VALUE_MAX) {
|
||||
binding.messageInputView.micInputCloud.setRotationSpeed(
|
||||
log10(d).toFloat(),
|
||||
MicInputCloud.MAXIMUM_RADIUS
|
||||
)
|
||||
} else if (d > AUDIO_VALUE_MIN) {
|
||||
binding.messageInputView.micInputCloud.setRotationSpeed(
|
||||
log10(d).toFloat(),
|
||||
MicInputCloud.EXTENDED_RADIUS
|
||||
)
|
||||
} else {
|
||||
binding.messageInputView.micInputCloud.setRotationSpeed(
|
||||
1f,
|
||||
MicInputCloud.DEFAULT_RADIUS
|
||||
)
|
||||
}
|
||||
Thread.sleep(AUDIO_VALUE_SLEEP)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun stopMicInputRecordingAnimation() {
|
||||
if (micInputAudioRecordThread != null) {
|
||||
Log.d(TAG, "Mic Animation Ended")
|
||||
@ -2181,10 +2169,19 @@ class ChatActivity :
|
||||
animation.repeatMode = Animation.REVERSE
|
||||
binding.messageInputView.microphoneEnabledInfo.startAnimation(animation)
|
||||
|
||||
initMediaRecorder(file)
|
||||
VibrationUtils.vibrateShort(context)
|
||||
}
|
||||
|
||||
private fun initMediaRecorder(file: String) {
|
||||
recorder = MediaRecorder().apply {
|
||||
setAudioSource(MediaRecorder.AudioSource.MIC)
|
||||
setOutputFile(file)
|
||||
mediaRecorderState = MediaRecorderState.INITIALIZED
|
||||
|
||||
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
|
||||
mediaRecorderState = MediaRecorderState.CONFIGURED
|
||||
|
||||
setOutputFile(file)
|
||||
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
|
||||
setAudioSamplingRate(VOICE_MESSAGE_SAMPLING_RATE)
|
||||
setAudioEncodingBitRate(VOICE_MESSAGE_ENCODING_BIT_RATE)
|
||||
@ -2192,35 +2189,44 @@ class ChatActivity :
|
||||
|
||||
try {
|
||||
prepare()
|
||||
mediaRecorderState = MediaRecorderState.PREPARED
|
||||
} catch (e: IOException) {
|
||||
mediaRecorderState = MediaRecorderState.ERROR
|
||||
Log.e(TAG, "prepare for audio recording failed")
|
||||
}
|
||||
|
||||
try {
|
||||
start()
|
||||
mediaRecorderState = MediaRecorderState.RECORDING
|
||||
Log.d(TAG, "recording started")
|
||||
isVoiceRecordingInProgress = true
|
||||
} catch (e: IllegalStateException) {
|
||||
mediaRecorderState = MediaRecorderState.ERROR
|
||||
Log.e(TAG, "start for audio recording failed")
|
||||
}
|
||||
|
||||
VibrationUtils.vibrateShort(context)
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopAndSendAudioRecording() {
|
||||
stopAudioRecording()
|
||||
Log.d(TAG, "stopped and sent audio recording")
|
||||
val uri = Uri.fromFile(File(currentVoiceRecordFile))
|
||||
uploadFile(uri.toString(), true)
|
||||
|
||||
if (mediaRecorderState != MediaRecorderState.ERROR) {
|
||||
val uri = Uri.fromFile(File(currentVoiceRecordFile))
|
||||
uploadFile(uri.toString(), true)
|
||||
} else {
|
||||
mediaRecorderState = MediaRecorderState.INITIAL
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopAndDiscardAudioRecording() {
|
||||
stopAudioRecording()
|
||||
Log.d(TAG, "stopped and discarded audio recording")
|
||||
|
||||
val cachedFile = File(currentVoiceRecordFile)
|
||||
cachedFile.delete()
|
||||
|
||||
if (mediaRecorderState == MediaRecorderState.ERROR) {
|
||||
mediaRecorderState = MediaRecorderState.INITIAL
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
@ -2230,17 +2236,22 @@ class ChatActivity :
|
||||
|
||||
recorder?.apply {
|
||||
try {
|
||||
Log.d(TAG, "recording stopped with $voiceRecordDuration")
|
||||
if (voiceRecordDuration > MINIMUM_VOICE_RECORD_TO_STOP) {
|
||||
if (mediaRecorderState == MediaRecorderState.RECORDING) {
|
||||
stop()
|
||||
reset()
|
||||
mediaRecorderState = MediaRecorderState.INITIAL
|
||||
Log.d(TAG, "stopped recorder")
|
||||
}
|
||||
release()
|
||||
isVoiceRecordingInProgress = false
|
||||
Log.d(TAG, "stopped recorder. isVoiceRecordingInProgress = false")
|
||||
} catch (e: java.lang.IllegalStateException) {
|
||||
error("error while stopping recorder!" + e)
|
||||
} catch (e: java.lang.RuntimeException) {
|
||||
error("error while stopping recorder!" + e)
|
||||
mediaRecorderState = MediaRecorderState.RELEASED
|
||||
} catch (e: Exception) {
|
||||
when (e) {
|
||||
is java.lang.IllegalStateException,
|
||||
is java.lang.RuntimeException -> {
|
||||
mediaRecorderState = MediaRecorderState.ERROR
|
||||
Log.e(TAG, "error while stopping recorder! with state $mediaRecorderState $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VibrationUtils.vibrateShort(context)
|
||||
@ -2404,7 +2415,9 @@ class ChatActivity :
|
||||
} else {
|
||||
binding.lobby.lobbyView.visibility = View.GONE
|
||||
binding.messagesListView.visibility = View.VISIBLE
|
||||
binding.messageInputView.inputEditText?.visibility = View.VISIBLE
|
||||
if (!isVoiceRecordingLocked) {
|
||||
binding.messageInputView.inputEditText?.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2459,40 +2472,12 @@ class ChatActivity :
|
||||
filenamesWithLineBreaks.append(filename).append("\n")
|
||||
}
|
||||
|
||||
val confirmationQuestion = when (filesToUpload.size) {
|
||||
1 -> context.resources?.getString(R.string.nc_upload_confirm_send_single)?.let {
|
||||
String.format(it, title.trim())
|
||||
}
|
||||
|
||||
else -> context.resources?.getString(R.string.nc_upload_confirm_send_multiple)?.let {
|
||||
String.format(it, title.trim())
|
||||
}
|
||||
}
|
||||
|
||||
binding.messageInputView.context?.let {
|
||||
val materialAlertDialogBuilder = MaterialAlertDialogBuilder(it)
|
||||
.setTitle(confirmationQuestion)
|
||||
.setMessage(filenamesWithLineBreaks.toString())
|
||||
.setPositiveButton(R.string.nc_yes) { _, _ ->
|
||||
if (permissionUtil.isFilesPermissionGranted()) {
|
||||
uploadFiles(filesToUpload)
|
||||
} else {
|
||||
UploadAndShareFilesWorker.requestStoragePermission(this)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.nc_no) { _, _ ->
|
||||
// unused atm
|
||||
}
|
||||
|
||||
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it, materialAlertDialogBuilder)
|
||||
|
||||
val dialog = materialAlertDialogBuilder.show()
|
||||
|
||||
viewThemeUtils.platform.colorTextButtons(
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
|
||||
)
|
||||
}
|
||||
val newFragment: DialogFragment = FileAttachmentPreviewFragment.newInstance(
|
||||
filenamesWithLineBreaks.toString(),
|
||||
filesToUpload,
|
||||
this::uploadFiles
|
||||
)
|
||||
newFragment.show(supportFragmentManager, FileAttachmentPreviewFragment.TAG)
|
||||
} catch (e: IllegalStateException) {
|
||||
context.resources?.getString(R.string.nc_upload_failed)?.let {
|
||||
Snackbar.make(
|
||||
@ -2554,7 +2539,19 @@ class ChatActivity :
|
||||
}
|
||||
|
||||
if (permissionUtil.isFilesPermissionGranted()) {
|
||||
uploadFiles(filesToUpload)
|
||||
val filenamesWithLineBreaks = StringBuilder("\n")
|
||||
|
||||
for (file in filesToUpload) {
|
||||
val filename = FileUtils.getFileName(Uri.parse(file), context)
|
||||
filenamesWithLineBreaks.append(filename).append("\n")
|
||||
}
|
||||
|
||||
val newFragment: DialogFragment = FileAttachmentPreviewFragment.newInstance(
|
||||
filenamesWithLineBreaks.toString(),
|
||||
filesToUpload,
|
||||
this::uploadFiles
|
||||
)
|
||||
newFragment.show(supportFragmentManager, FileAttachmentPreviewFragment.TAG)
|
||||
} else {
|
||||
UploadAndShareFilesWorker.requestStoragePermission(this)
|
||||
}
|
||||
@ -2625,7 +2622,7 @@ class ChatActivity :
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == UploadAndShareFilesWorker.REQUEST_PERMISSION) {
|
||||
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
Log.d(ConversationsListActivity.TAG, "upload starting after permissions were granted")
|
||||
Log.d(TAG, "upload starting after permissions were granted")
|
||||
if (filesToUpload.isNotEmpty()) {
|
||||
uploadFiles(filesToUpload)
|
||||
}
|
||||
@ -2678,13 +2675,17 @@ class ChatActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private fun uploadFiles(files: MutableList<String>) {
|
||||
for (file in files) {
|
||||
uploadFile(file, false)
|
||||
private fun uploadFiles(files: MutableList<String>, caption: String = "") {
|
||||
for (i in 0 until files.size) {
|
||||
if (i == files.size - 1) {
|
||||
uploadFile(files[i], false, caption)
|
||||
} else {
|
||||
uploadFile(files[i], false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun uploadFile(fileUri: String, isVoiceMessage: Boolean) {
|
||||
private fun uploadFile(fileUri: String, isVoiceMessage: Boolean, caption: String = "") {
|
||||
var metaData = ""
|
||||
|
||||
if (!participantPermissions.hasChatPermission()) {
|
||||
@ -2696,6 +2697,10 @@ class ChatActivity :
|
||||
metaData = VOICE_MESSAGE_META_DATA
|
||||
}
|
||||
|
||||
if (caption != "") {
|
||||
metaData = "{\"caption\":\"$caption\"}"
|
||||
}
|
||||
|
||||
try {
|
||||
require(fileUri.isNotEmpty())
|
||||
UploadAndShareFilesWorker.upload(
|
||||
@ -2986,9 +2991,7 @@ class ChatActivity :
|
||||
}
|
||||
}
|
||||
|
||||
fun leaveRoom(
|
||||
funToCallWhenLeaveSuccessful: (() -> Unit)?
|
||||
) {
|
||||
fun leaveRoom(funToCallWhenLeaveSuccessful: (() -> Unit)?) {
|
||||
logConversationInfos("leaveRoom")
|
||||
|
||||
var apiVersion = 1
|
||||
@ -3154,11 +3157,7 @@ class ChatActivity :
|
||||
signalingMessageSender = webSocketInstance?.signalingMessageSender
|
||||
}
|
||||
|
||||
fun pullChatMessages(
|
||||
lookIntoFuture: Boolean,
|
||||
setReadMarker: Boolean = true,
|
||||
xChatLastCommonRead: Int? = null
|
||||
) {
|
||||
fun pullChatMessages(lookIntoFuture: Boolean, setReadMarker: Boolean = true, xChatLastCommonRead: Int? = null) {
|
||||
if (!validSessionId()) {
|
||||
return
|
||||
}
|
||||
@ -3258,30 +3257,7 @@ class ChatActivity :
|
||||
Integer.parseInt(it)
|
||||
}
|
||||
|
||||
try {
|
||||
val mostRecentCallSystemMessage = adapter?.items?.first {
|
||||
it.item is ChatMessage &&
|
||||
(it.item as ChatMessage).systemMessageType in
|
||||
listOf(
|
||||
ChatMessage.SystemMessageType.CALL_STARTED,
|
||||
ChatMessage.SystemMessageType.CALL_JOINED,
|
||||
ChatMessage.SystemMessageType.CALL_LEFT,
|
||||
ChatMessage.SystemMessageType.CALL_ENDED,
|
||||
ChatMessage.SystemMessageType.CALL_TRIED,
|
||||
ChatMessage.SystemMessageType.CALL_ENDED_EVERYONE,
|
||||
ChatMessage.SystemMessageType.CALL_MISSED
|
||||
)
|
||||
}?.item
|
||||
|
||||
if (mostRecentCallSystemMessage != null) {
|
||||
processMostRecentMessage(
|
||||
mostRecentCallSystemMessage as ChatMessage,
|
||||
chatMessageList
|
||||
)
|
||||
}
|
||||
} catch (e: java.util.NoSuchElementException) {
|
||||
Log.d(TAG, "No System messages found $e")
|
||||
}
|
||||
processCallStartedMessages(chatMessageList)
|
||||
|
||||
updateReadStatusOfAllMessages(newXChatLastCommonRead)
|
||||
adapter?.notifyDataSetChanged()
|
||||
@ -3316,6 +3292,33 @@ class ChatActivity :
|
||||
})
|
||||
}
|
||||
|
||||
private fun processCallStartedMessages(chatMessageList: List<ChatMessage>) {
|
||||
try {
|
||||
val mostRecentCallSystemMessage = adapter?.items?.first {
|
||||
it.item is ChatMessage &&
|
||||
(it.item as ChatMessage).systemMessageType in
|
||||
listOf(
|
||||
ChatMessage.SystemMessageType.CALL_STARTED,
|
||||
ChatMessage.SystemMessageType.CALL_JOINED,
|
||||
ChatMessage.SystemMessageType.CALL_LEFT,
|
||||
ChatMessage.SystemMessageType.CALL_ENDED,
|
||||
ChatMessage.SystemMessageType.CALL_TRIED,
|
||||
ChatMessage.SystemMessageType.CALL_ENDED_EVERYONE,
|
||||
ChatMessage.SystemMessageType.CALL_MISSED
|
||||
)
|
||||
}?.item
|
||||
|
||||
if (mostRecentCallSystemMessage != null) {
|
||||
processMostRecentMessage(
|
||||
mostRecentCallSystemMessage as ChatMessage,
|
||||
chatMessageList
|
||||
)
|
||||
}
|
||||
} catch (e: NoSuchElementException) {
|
||||
Log.d(TAG, "No System messages found $e")
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFieldsForPullChatMessages(
|
||||
lookIntoFuture: Boolean,
|
||||
xChatLastCommonRead: Int?,
|
||||
@ -3474,10 +3477,7 @@ class ChatActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private fun addMessagesToAdapter(
|
||||
shouldAddNewMessagesNotice: Boolean,
|
||||
chatMessageList: List<ChatMessage>
|
||||
) {
|
||||
private fun addMessagesToAdapter(shouldAddNewMessagesNotice: Boolean, chatMessageList: List<ChatMessage>) {
|
||||
val isThereANewNotice =
|
||||
shouldAddNewMessagesNotice || adapter?.getMessagePositionByIdInReverse("-1") != -1
|
||||
for (chatMessage in chatMessageList) {
|
||||
@ -3730,19 +3730,15 @@ class ChatActivity :
|
||||
chatMessageMap[currentMessage.value.parentMessage!!.id]!!.isDeleted = true
|
||||
}
|
||||
chatMessageIterator.remove()
|
||||
}
|
||||
|
||||
// delete reactions system messages
|
||||
else if (isReactionsMessage(currentMessage)) {
|
||||
} else if (isReactionsMessage(currentMessage)) {
|
||||
// delete reactions system messages
|
||||
if (!chatMessageMap.containsKey(currentMessage.value.parentMessage!!.id)) {
|
||||
updateAdapterForReaction(currentMessage.value.parentMessage)
|
||||
}
|
||||
|
||||
chatMessageIterator.remove()
|
||||
}
|
||||
|
||||
// delete poll system messages
|
||||
else if (isPollVotedMessage(currentMessage)) {
|
||||
} else if (isPollVotedMessage(currentMessage)) {
|
||||
// delete poll system messages
|
||||
chatMessageIterator.remove()
|
||||
}
|
||||
}
|
||||
@ -4155,27 +4151,14 @@ class ChatActivity :
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveImage(message: ChatMessage) {
|
||||
if (permissionUtil.isFilesPermissionGranted()) {
|
||||
saveImageToStorage(message)
|
||||
} else {
|
||||
UploadAndShareFilesWorker.requestStoragePermission(this@ChatActivity)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSaveToStorageWarning(message: ChatMessage) {
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle(R.string.nc_dialog_save_to_storage_title)
|
||||
builder.setMessage(R.string.nc_dialog_save_to_storage_content)
|
||||
builder.setPositiveButton(R.string.nc_dialog_save_to_storage_yes) { dialog: DialogInterface, _: Int ->
|
||||
saveImage(message)
|
||||
dialog.dismiss()
|
||||
}
|
||||
builder.setNegativeButton(R.string.nc_dialog_save_to_storage_no) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
val saveFragment: DialogFragment = SaveToStorageDialogFragment.newInstance(
|
||||
message.selectedIndividualHashMap!!["name"]!!
|
||||
)
|
||||
saveFragment.show(
|
||||
supportFragmentManager,
|
||||
SaveToStorageDialogFragment.TAG
|
||||
)
|
||||
}
|
||||
|
||||
fun checkIfSaveable(message: ChatMessage) {
|
||||
@ -4616,5 +4599,6 @@ class ChatActivity :
|
||||
private const val TYPING_STOPPED_SIGNALING_MESSAGE_TYPE = "stoppedTyping"
|
||||
private const val CALL_STARTED_ID = -2
|
||||
private const val MILISEC_15: Long = 15
|
||||
private const val LINEBREAK = "\n"
|
||||
}
|
||||
}
|
||||
|
@ -29,10 +29,7 @@ import com.nextcloud.talk.utils.ApiUtils
|
||||
import io.reactivex.Observable
|
||||
|
||||
class ChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository {
|
||||
override fun getRoom(
|
||||
user: User,
|
||||
roomToken: String
|
||||
): Observable<ConversationModel> {
|
||||
override fun getRoom(user: User, roomToken: String): Observable<ConversationModel> {
|
||||
val credentials: String = ApiUtils.getCredentials(user.username, user.token)
|
||||
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv3, 1))
|
||||
|
||||
@ -42,11 +39,7 @@ class ChatRepositoryImpl(private val ncApi: NcApi) : ChatRepository {
|
||||
).map { ConversationModel.mapToConversationModel(it.ocs?.data!!) }
|
||||
}
|
||||
|
||||
override fun joinRoom(
|
||||
user: User,
|
||||
roomToken: String,
|
||||
roomPassword: String
|
||||
): Observable<ConversationModel> {
|
||||
override fun joinRoom(user: User, roomToken: String, roomPassword: String): Observable<ConversationModel> {
|
||||
val credentials: String = ApiUtils.getCredentials(user.username, user.token)
|
||||
val apiVersion = ApiUtils.getConversationApiVersion(user, intArrayOf(ApiUtils.APIv4, 1))
|
||||
|
||||
|
@ -72,7 +72,7 @@ class ChatViewModel @Inject constructor(private val repository: ChatRepository)
|
||||
}
|
||||
|
||||
fun joinRoom(user: User, token: String, roomPassword: String) {
|
||||
_getRoomViewState.value = JoinRoomStartState
|
||||
_joinRoomViewState.value = JoinRoomStartState
|
||||
repository.joinRoom(user, token, roomPassword)
|
||||
.subscribeOn(Schedulers.io())
|
||||
?.observeOn(AndroidSchedulers.mainThread())
|
||||
|
@ -488,17 +488,13 @@ class ContactsActivity :
|
||||
} else {
|
||||
adapter?.filterItems()
|
||||
}
|
||||
|
||||
binding.controllerGenericRv.swipeRefreshLayout.isRefreshing = false
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
binding.controllerGenericRv.swipeRefreshLayout.isRefreshing = false
|
||||
dispose(contactsQueryDisposable)
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
binding.controllerGenericRv.swipeRefreshLayout.isRefreshing = false
|
||||
dispose(contactsQueryDisposable)
|
||||
alreadyFetching = false
|
||||
disengageProgressBar()
|
||||
@ -656,12 +652,9 @@ class ContactsActivity :
|
||||
|
||||
private fun prepareViews() {
|
||||
layoutManager = SmoothScrollLinearLayoutManager(this)
|
||||
binding.controllerGenericRv.recyclerView.layoutManager = layoutManager
|
||||
binding.controllerGenericRv.recyclerView.setHasFixedSize(true)
|
||||
binding.controllerGenericRv.recyclerView.adapter = adapter
|
||||
binding.controllerGenericRv.swipeRefreshLayout.setOnRefreshListener { fetchData() }
|
||||
|
||||
binding.controllerGenericRv.let { viewThemeUtils.androidx.themeSwipeRefreshLayout(it.swipeRefreshLayout) }
|
||||
binding.contactsRv.layoutManager = layoutManager
|
||||
binding.contactsRv.setHasFixedSize(true)
|
||||
binding.contactsRv.adapter = adapter
|
||||
|
||||
binding.listOpenConversationsImage.background?.setColorFilter(
|
||||
ResourcesCompat.getColor(resources!!, R.color.colorBackgroundDarker, null),
|
||||
@ -677,7 +670,7 @@ class ContactsActivity :
|
||||
private fun disengageProgressBar() {
|
||||
if (!alreadyFetching) {
|
||||
binding.loadingContent.visibility = View.GONE
|
||||
binding.controllerGenericRv.root.visibility = View.VISIBLE
|
||||
binding.root.visibility = View.VISIBLE
|
||||
if (isNewConversationView) {
|
||||
binding.callHeaderLayout.visibility = View.VISIBLE
|
||||
}
|
||||
@ -713,8 +706,6 @@ class ContactsActivity :
|
||||
adapter?.updateDataSet(contactItems as List<Nothing>?)
|
||||
}
|
||||
|
||||
binding.controllerGenericRv?.swipeRefreshLayout?.isEnabled = !adapter!!.hasFilter()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -1,256 +0,0 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* @author Andy Scherzinger
|
||||
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.nextcloud.talk.controllers
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.media.MediaPlayer
|
||||
import android.media.RingtoneManager
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import autodagger.AutoInjector
|
||||
import com.bluelinelabs.logansquare.LoganSquare
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.adapters.items.NotificationSoundItem
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.controllers.base.BaseController
|
||||
import com.nextcloud.talk.controllers.util.viewBinding
|
||||
import com.nextcloud.talk.databinding.ControllerGenericRvBinding
|
||||
import com.nextcloud.talk.models.RingtoneSettings
|
||||
import com.nextcloud.talk.utils.NotificationUtils
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ARE_CALL_SOUNDS
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.SelectableAdapter
|
||||
import eu.davidea.flexibleadapter.common.SmoothScrollLinearLayoutManager
|
||||
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
|
||||
import java.io.IOException
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class RingtoneSelectionController(args: Bundle) :
|
||||
BaseController(
|
||||
R.layout.controller_generic_rv,
|
||||
args
|
||||
),
|
||||
FlexibleAdapter.OnItemClickListener {
|
||||
private val binding: ControllerGenericRvBinding? by viewBinding(ControllerGenericRvBinding::bind)
|
||||
|
||||
private var adapter: FlexibleAdapter<*>? = null
|
||||
private var adapterDataObserver: RecyclerView.AdapterDataObserver? = null
|
||||
private val abstractFlexibleItemList: MutableList<AbstractFlexibleItem<*>> = ArrayList()
|
||||
private val callNotificationSounds: Boolean
|
||||
private var mediaPlayer: MediaPlayer? = null
|
||||
private var cancelMediaPlayerHandler: Handler? = null
|
||||
|
||||
override fun onViewBound(view: View) {
|
||||
super.onViewBound(view)
|
||||
if (adapter == null) {
|
||||
adapter = FlexibleAdapter(abstractFlexibleItemList, activity, false)
|
||||
adapter!!.setNotifyChangeOfUnfilteredItems(true).mode = SelectableAdapter.Mode.SINGLE
|
||||
adapter!!.addListener(this)
|
||||
cancelMediaPlayerHandler = Handler()
|
||||
}
|
||||
adapter!!.addListener(this)
|
||||
prepareViews()
|
||||
fetchNotificationSounds()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == android.R.id.home) {
|
||||
router.popCurrentController()
|
||||
} else {
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun prepareViews() {
|
||||
val layoutManager: RecyclerView.LayoutManager = SmoothScrollLinearLayoutManager(activity)
|
||||
binding?.recyclerView?.layoutManager = layoutManager
|
||||
binding?.recyclerView?.setHasFixedSize(true)
|
||||
binding?.recyclerView?.adapter = adapter
|
||||
adapterDataObserver = object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onChanged() {
|
||||
super.onChanged()
|
||||
findSelectedSound()
|
||||
}
|
||||
}
|
||||
adapter!!.registerAdapterDataObserver(adapterDataObserver!!)
|
||||
binding?.swipeRefreshLayout?.isEnabled = false
|
||||
}
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
private fun findSelectedSound() {
|
||||
var foundDefault = false
|
||||
var preferencesString: String? = null
|
||||
val callsEnabledButNoRingtone = callNotificationSounds &&
|
||||
TextUtils.isEmpty(appPreferences.callRingtoneUri.also { preferencesString = it })
|
||||
val noCallsAndNoMessageTone = !callNotificationSounds &&
|
||||
TextUtils.isEmpty(appPreferences.messageRingtoneUri.also { preferencesString = it })
|
||||
if (callsEnabledButNoRingtone || noCallsAndNoMessageTone) {
|
||||
adapter!!.toggleSelection(1)
|
||||
foundDefault = true
|
||||
}
|
||||
if (!TextUtils.isEmpty(preferencesString) && !foundDefault) {
|
||||
try {
|
||||
val ringtoneSettings: RingtoneSettings =
|
||||
LoganSquare.parse<RingtoneSettings>(preferencesString, RingtoneSettings::class.java)
|
||||
if (ringtoneSettings.ringtoneUri == null) {
|
||||
adapter!!.toggleSelection(0)
|
||||
} else if (ringtoneSettings.ringtoneUri!!.toString() == ringtoneString) {
|
||||
adapter!!.toggleSelection(1)
|
||||
} else {
|
||||
var notificationSoundItem: NotificationSoundItem?
|
||||
for (i in 2 until adapter!!.itemCount) {
|
||||
notificationSoundItem = adapter!!.getItem(i) as NotificationSoundItem?
|
||||
if (
|
||||
notificationSoundItem!!.notificationSoundUri == ringtoneSettings.ringtoneUri!!.toString()
|
||||
) {
|
||||
adapter!!.toggleSelection(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Failed to parse ringtone settings")
|
||||
}
|
||||
}
|
||||
adapter!!.unregisterAdapterDataObserver(adapterDataObserver!!)
|
||||
adapterDataObserver = null
|
||||
}
|
||||
|
||||
private val ringtoneString: String
|
||||
get() = if (callNotificationSounds) {
|
||||
NotificationUtils.DEFAULT_CALL_RINGTONE_URI
|
||||
} else {
|
||||
NotificationUtils.DEFAULT_MESSAGE_RINGTONE_URI
|
||||
}
|
||||
|
||||
private fun fetchNotificationSounds() {
|
||||
abstractFlexibleItemList.add(
|
||||
NotificationSoundItem(
|
||||
resources!!.getString(R.string.nc_settings_no_ringtone),
|
||||
null
|
||||
)
|
||||
)
|
||||
abstractFlexibleItemList.add(
|
||||
NotificationSoundItem(
|
||||
resources!!.getString(R.string.nc_settings_default_ringtone),
|
||||
ringtoneString
|
||||
)
|
||||
)
|
||||
if (activity != null) {
|
||||
val manager = RingtoneManager(activity)
|
||||
if (callNotificationSounds) {
|
||||
manager.setType(RingtoneManager.TYPE_RINGTONE)
|
||||
} else {
|
||||
manager.setType(RingtoneManager.TYPE_NOTIFICATION)
|
||||
}
|
||||
val cursor = manager.cursor
|
||||
var notificationSoundItem: NotificationSoundItem
|
||||
while (cursor.moveToNext()) {
|
||||
val notificationTitle = cursor.getString(RingtoneManager.TITLE_COLUMN_INDEX)
|
||||
val notificationUri = cursor.getString(RingtoneManager.URI_COLUMN_INDEX)
|
||||
val completeNotificationUri = notificationUri + "/" + cursor.getString(RingtoneManager.ID_COLUMN_INDEX)
|
||||
notificationSoundItem = NotificationSoundItem(notificationTitle, completeNotificationUri)
|
||||
abstractFlexibleItemList.add(notificationSoundItem)
|
||||
}
|
||||
}
|
||||
adapter!!.updateDataSet(abstractFlexibleItemList as List<Nothing>?, false)
|
||||
}
|
||||
|
||||
override fun onItemClick(view: View, position: Int): Boolean {
|
||||
val notificationSoundItem = adapter!!.getItem(position) as NotificationSoundItem?
|
||||
var ringtoneUri: Uri? = null
|
||||
if (!TextUtils.isEmpty(notificationSoundItem!!.notificationSoundUri)) {
|
||||
ringtoneUri = Uri.parse(notificationSoundItem.notificationSoundUri)
|
||||
endMediaPlayer()
|
||||
mediaPlayer = MediaPlayer.create(activity, ringtoneUri)
|
||||
cancelMediaPlayerHandler = Handler()
|
||||
cancelMediaPlayerHandler!!.postDelayed(
|
||||
{ endMediaPlayer() },
|
||||
(mediaPlayer!!.duration + DURATION_EXTENSION).toLong()
|
||||
)
|
||||
mediaPlayer!!.start()
|
||||
}
|
||||
if (adapter!!.selectedPositions.size == 0 || adapter!!.selectedPositions[0] != position) {
|
||||
val ringtoneSettings = RingtoneSettings()
|
||||
ringtoneSettings.ringtoneName = notificationSoundItem.notificationSoundName
|
||||
ringtoneSettings.ringtoneUri = ringtoneUri
|
||||
if (callNotificationSounds) {
|
||||
try {
|
||||
appPreferences!!.callRingtoneUri = LoganSquare.serialize(ringtoneSettings)
|
||||
adapter!!.toggleSelection(position)
|
||||
adapter!!.notifyDataSetChanged()
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Failed to store selected ringtone for calls")
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
appPreferences!!.messageRingtoneUri = LoganSquare.serialize(ringtoneSettings)
|
||||
adapter!!.toggleSelection(position)
|
||||
adapter!!.notifyDataSetChanged()
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Failed to store selected ringtone for calls")
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun endMediaPlayer() {
|
||||
if (cancelMediaPlayerHandler != null) {
|
||||
cancelMediaPlayerHandler!!.removeCallbacksAndMessages(null)
|
||||
}
|
||||
if (mediaPlayer != null) {
|
||||
if (mediaPlayer!!.isPlaying) {
|
||||
mediaPlayer!!.stop()
|
||||
}
|
||||
mediaPlayer!!.release()
|
||||
mediaPlayer = null
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onDestroy() {
|
||||
endMediaPlayer()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "RingtoneSelection"
|
||||
private const val DURATION_EXTENSION = 25
|
||||
}
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
callNotificationSounds = args.getBoolean(KEY_ARE_CALL_SOUNDS, false)
|
||||
}
|
||||
|
||||
override val title: String
|
||||
get() =
|
||||
resources!!.getString(R.string.nc_settings_notification_sounds)
|
||||
}
|
@ -1,309 +0,0 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Andy Scherzinger
|
||||
* @author BlueLine Labs, Inc.
|
||||
* @author Mario Danic
|
||||
* Copyright (C) 2021 Andy Scherzinger (info@andy-scherzinger.de)
|
||||
* Copyright (C) 2021 BlueLine Labs, Inc.
|
||||
* Copyright (C) 2020 Mario Danic (mario@lovelyhq.com)
|
||||
*
|
||||
* 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 com.nextcloud.talk.controllers.base
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.ActionBar
|
||||
import autodagger.AutoInjector
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
import com.bluelinelabs.conductor.ControllerChangeType
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.MainActivity
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.controllers.AccountVerificationController
|
||||
import com.nextcloud.talk.controllers.ServerSelectionController
|
||||
import com.nextcloud.talk.controllers.SwitchAccountController
|
||||
import com.nextcloud.talk.controllers.WebViewLoginController
|
||||
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
|
||||
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import javax.inject.Inject
|
||||
import kotlin.jvm.internal.Intrinsics
|
||||
|
||||
// TODO: check what needs to be migrated from this class to BaseActivity etc when conductor is removed
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
abstract class BaseController(@LayoutRes var layoutRes: Int, args: Bundle? = null) : Controller(args) {
|
||||
enum class AppBarLayoutType {
|
||||
TOOLBAR, SEARCH_BAR, EMPTY
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var appPreferences: AppPreferences
|
||||
|
||||
@Inject
|
||||
lateinit var context: Context
|
||||
|
||||
@Inject
|
||||
lateinit var viewThemeUtils: ViewThemeUtils
|
||||
|
||||
protected open val title: String?
|
||||
get() = null
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
protected val actionBar: ActionBar?
|
||||
get() {
|
||||
var actionBarProvider: ActionBarProvider? = null
|
||||
if (this.activity is ActionBarProvider) {
|
||||
try {
|
||||
actionBarProvider = this.activity as ActionBarProvider?
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "Failed to fetch the action bar provider", e)
|
||||
}
|
||||
}
|
||||
return actionBarProvider?.supportActionBar
|
||||
}
|
||||
|
||||
init {
|
||||
@Suppress("LeakingThis")
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
addLifecycleListener(object : LifecycleListener() {
|
||||
override fun postCreateView(controller: Controller, view: View) {
|
||||
onViewBound(view)
|
||||
actionBar?.let { setTitle() }
|
||||
}
|
||||
})
|
||||
cleanTempCertPreference()
|
||||
}
|
||||
|
||||
fun isAlive(): Boolean {
|
||||
return !isDestroyed && !isBeingDestroyed
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup,
|
||||
savedViewState: Bundle?
|
||||
): View {
|
||||
return inflater.inflate(layoutRes, container, false)
|
||||
}
|
||||
|
||||
protected open fun onViewBound(view: View) {
|
||||
var activity: MainActivity? = null
|
||||
|
||||
// if (getActivity() != null && getActivity() is MainActivity) {
|
||||
// activity = getActivity() as MainActivity?
|
||||
// viewThemeUtils.material.themeCardView(activity!!.binding.searchToolbar)
|
||||
// viewThemeUtils.material.themeToolbar(activity.binding.toolbar)
|
||||
// viewThemeUtils.material.themeSearchBarText(activity.binding.searchText)
|
||||
// }
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && appPreferences.isKeyboardIncognito) {
|
||||
disableKeyboardPersonalisedLearning((view as ViewGroup))
|
||||
if (activity != null) {
|
||||
disableKeyboardPersonalisedLearning(activity.binding.appBar)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(view: View) {
|
||||
// showSearchOrToolbar()
|
||||
setTitle()
|
||||
if (actionBar != null) {
|
||||
actionBar!!.setDisplayHomeAsUpEnabled(parentController != null || router.backstackSize >= 1)
|
||||
}
|
||||
super.onAttach(view)
|
||||
}
|
||||
|
||||
// open fun showSearchOrToolbar() {
|
||||
// if (isValidActivity(activity)) {
|
||||
// val showSearchBar = appBarLayoutType == AppBarLayoutType.SEARCH_BAR
|
||||
// val activity = activity as MainActivity
|
||||
//
|
||||
// if (appBarLayoutType == AppBarLayoutType.EMPTY) {
|
||||
// hideBars(activity.binding)
|
||||
// } else {
|
||||
// if (showSearchBar) {
|
||||
// showSearchBar(activity.binding)
|
||||
// } else {
|
||||
// showToolbar(activity.binding)
|
||||
// }
|
||||
// colorizeStatusBar(showSearchBar, activity, resources)
|
||||
// }
|
||||
//
|
||||
// colorizeNavigationBar(activity, resources)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private fun isValidActivity(activity: Activity?): Boolean {
|
||||
// return activity != null && activity is MainActivity
|
||||
// }
|
||||
//
|
||||
// private fun showSearchBar(binding: ActivityMainBinding) {
|
||||
// val layoutParams = binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
|
||||
// binding.searchToolbar.visibility = View.VISIBLE
|
||||
// binding.searchText.hint = searchHint
|
||||
// binding.toolbar.visibility = View.GONE
|
||||
// // layoutParams.setScrollFlags(AppBarLayout.LayoutParams.SCROLL_FLAG_SCROLL | AppBarLayout
|
||||
// // .LayoutParams.SCROLL_FLAG_SNAP | AppBarLayout.LayoutParams.SCROLL_FLAG_ENTER_ALWAYS);
|
||||
// layoutParams.scrollFlags = 0
|
||||
// binding.appBar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
|
||||
// binding.appBar.context,
|
||||
// R.animator.appbar_elevation_off
|
||||
// )
|
||||
// binding.searchToolbar.layoutParams = layoutParams
|
||||
// }
|
||||
//
|
||||
// private fun showToolbar(binding: ActivityMainBinding) {
|
||||
// val layoutParams = binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
|
||||
// binding.searchToolbar.visibility = View.GONE
|
||||
// binding.toolbar.visibility = View.VISIBLE
|
||||
// viewThemeUtils.material.colorToolbarOverflowIcon(binding.toolbar)
|
||||
// layoutParams.scrollFlags = 0
|
||||
// binding.appBar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
|
||||
// binding.appBar.context,
|
||||
// R.animator.appbar_elevation_on
|
||||
// )
|
||||
// binding.searchToolbar.layoutParams = layoutParams
|
||||
// }
|
||||
//
|
||||
// private fun hideBars(binding: ActivityMainBinding) {
|
||||
// binding.toolbar.visibility = View.GONE
|
||||
// binding.searchToolbar.visibility = View.GONE
|
||||
// }
|
||||
//
|
||||
// fun hideSearchBar() {
|
||||
// val activity = activity as MainActivity?
|
||||
// val layoutParams = activity!!.binding.searchToolbar.layoutParams as AppBarLayout.LayoutParams
|
||||
// activity.binding.searchToolbar.visibility = View.GONE
|
||||
// activity.binding.toolbar.visibility = View.VISIBLE
|
||||
// layoutParams.scrollFlags = 0
|
||||
// activity.binding.appBar.stateListAnimator = AnimatorInflater.loadStateListAnimator(
|
||||
// activity.binding.appBar.context,
|
||||
// R.animator.appbar_elevation_on
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// private fun colorizeStatusBar(showSearchBar: Boolean, activity: Activity?, resources: Resources?) {
|
||||
// if (activity != null && resources != null) {
|
||||
// if (showSearchBar) {
|
||||
// view?.let { viewThemeUtils.platform.resetStatusBar(activity) }
|
||||
// } else {
|
||||
// view?.let { viewThemeUtils.platform.themeStatusBar(activity, it) }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private fun colorizeNavigationBar(activity: Activity?, resources: Resources?) {
|
||||
// if (activity != null && resources != null) {
|
||||
// DisplayUtils.applyColorToNavigationBar(
|
||||
// activity.window,
|
||||
// ResourcesCompat.getColor(resources, R.color.bg_default, null)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
||||
override fun onDetach(view: View) {
|
||||
super.onDetach(view)
|
||||
val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
imm.hideSoftInputFromWindow(view.windowToken, 0)
|
||||
}
|
||||
|
||||
protected fun setTitle() {
|
||||
if (isTitleSetable()) {
|
||||
run {
|
||||
calculateValidParentController()
|
||||
}
|
||||
actionBar!!.title = title
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateValidParentController() {
|
||||
var parentController = parentController
|
||||
while (parentController != null) {
|
||||
parentController = parentController.parentController
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTitleSetable(): Boolean {
|
||||
return title != null && actionBar != null
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
router.popCurrentController()
|
||||
return true
|
||||
}
|
||||
return super.onOptionsItemSelected(item)
|
||||
}
|
||||
|
||||
override fun onChangeStarted(changeHandler: ControllerChangeHandler, changeType: ControllerChangeType) {
|
||||
super.onChangeStarted(changeHandler, changeType)
|
||||
if (changeType.isEnter && actionBar != null) {
|
||||
configureMenu(actionBar!!)
|
||||
}
|
||||
}
|
||||
|
||||
fun configureMenu(toolbar: ActionBar) {
|
||||
Intrinsics.checkNotNullParameter(toolbar, "toolbar")
|
||||
}
|
||||
|
||||
// TODO: check if this must be migrated when using activities instead of conductor
|
||||
private fun cleanTempCertPreference() {
|
||||
val temporaryClassNames: MutableList<String> = ArrayList()
|
||||
temporaryClassNames.add(ServerSelectionController::class.java.name)
|
||||
temporaryClassNames.add(AccountVerificationController::class.java.name)
|
||||
temporaryClassNames.add(WebViewLoginController::class.java.name)
|
||||
temporaryClassNames.add(SwitchAccountController::class.java.name)
|
||||
if (!temporaryClassNames.contains(javaClass.name)) {
|
||||
appPreferences.removeTemporaryClientCertAlias()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private fun disableKeyboardPersonalisedLearning(viewGroup: ViewGroup) {
|
||||
var view: View?
|
||||
var editText: EditText
|
||||
for (i in 0 until viewGroup.childCount) {
|
||||
view = viewGroup.getChildAt(i)
|
||||
if (view is EditText) {
|
||||
editText = view
|
||||
editText.imeOptions = editText.imeOptions or EditorInfo.IME_FLAG_NO_PERSONALIZED_LEARNING
|
||||
} else if (view is ViewGroup) {
|
||||
disableKeyboardPersonalisedLearning(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open val appBarLayoutType: AppBarLayoutType
|
||||
get() = AppBarLayoutType.TOOLBAR
|
||||
val searchHint: String
|
||||
get() = context.getString(R.string.appbar_search_in, context.getString(R.string.nc_app_product_name))
|
||||
|
||||
companion object {
|
||||
private val TAG = BaseController::class.java.simpleName
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author BlueLine Labs, Inc.
|
||||
* Copyright (C) 2016 BlueLine Labs, Inc.
|
||||
*
|
||||
* 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 com.nextcloud.talk.controllers.util
|
||||
|
||||
import android.view.View
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.bluelinelabs.conductor.Controller
|
||||
import kotlin.properties.ReadOnlyProperty
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
fun <T : ViewBinding> Controller.viewBinding(bindingFactory: (View) -> T) =
|
||||
ControllerViewBindingDelegate(this, bindingFactory)
|
||||
|
||||
class ControllerViewBindingDelegate<T : ViewBinding>(
|
||||
controller: Controller,
|
||||
private val viewBinder: (View) -> T
|
||||
) : ReadOnlyProperty<Controller, T?>, LifecycleObserver {
|
||||
|
||||
private var binding: T? = null
|
||||
|
||||
init {
|
||||
controller.addLifecycleListener(object : Controller.LifecycleListener() {
|
||||
override fun postDestroyView(controller: Controller) {
|
||||
binding = null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun getValue(thisRef: Controller, property: KProperty<*>): T? {
|
||||
if (binding == null) {
|
||||
binding = thisRef.view?.let { viewBinder(it) }
|
||||
}
|
||||
return binding
|
||||
}
|
||||
}
|
@ -27,13 +27,7 @@ import io.reactivex.Observable
|
||||
|
||||
interface ConversationRepository {
|
||||
|
||||
fun renameConversation(
|
||||
roomToken: String,
|
||||
roomNameNew: String
|
||||
): Observable<GenericOverall>
|
||||
fun renameConversation(roomToken: String, roomNameNew: String): Observable<GenericOverall>
|
||||
|
||||
fun createConversation(
|
||||
roomName: String,
|
||||
conversationType: Conversation.ConversationType?
|
||||
): Observable<RoomOverall>
|
||||
fun createConversation(roomName: String, conversationType: Conversation.ConversationType?): Observable<RoomOverall>
|
||||
}
|
||||
|
@ -38,10 +38,7 @@ class ConversationRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
|
||||
val currentUser: User = currentUserProvider.currentUser.blockingGet()
|
||||
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
|
||||
|
||||
override fun renameConversation(
|
||||
roomToken: String,
|
||||
roomNameNew: String
|
||||
): Observable<GenericOverall> {
|
||||
override fun renameConversation(roomToken: String, roomNameNew: String): Observable<GenericOverall> {
|
||||
val apiVersion = ApiUtils.getConversationApiVersion(currentUser, intArrayOf(ApiUtils.APIv4, ApiUtils.APIv1))
|
||||
|
||||
return ncApi.renameRoom(
|
||||
|
@ -54,10 +54,7 @@ class ConversationViewModel @Inject constructor(private val repository: Conversa
|
||||
disposable?.dispose()
|
||||
}
|
||||
|
||||
fun createConversation(
|
||||
roomName: String,
|
||||
conversationType: Conversation.ConversationType?
|
||||
) {
|
||||
fun createConversation(roomName: String, conversationType: Conversation.ConversationType?) {
|
||||
_viewState.value = CreatingState
|
||||
|
||||
repository.createConversation(
|
||||
|
@ -57,9 +57,9 @@ import com.nextcloud.talk.activities.MainActivity
|
||||
import com.nextcloud.talk.adapters.items.ParticipantItem
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.bottomsheet.items.BasicListItemWithImage
|
||||
import com.nextcloud.talk.bottomsheet.items.listItemsWithImage
|
||||
import com.nextcloud.talk.contacts.ContactsActivity
|
||||
import com.nextcloud.talk.controllers.bottomsheet.items.BasicListItemWithImage
|
||||
import com.nextcloud.talk.controllers.bottomsheet.items.listItemsWithImage
|
||||
import com.nextcloud.talk.conversationinfoedit.ConversationInfoEditActivity
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.databinding.ActivityConversationInfoBinding
|
||||
@ -394,7 +394,8 @@ class ConversationInfoActivity :
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
// unused atm
|
||||
Log.e(TAG, "Failed to setLobbyForConversation", e)
|
||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -663,7 +664,15 @@ class ConversationInfoActivity :
|
||||
showOptionsMenu()
|
||||
} else {
|
||||
binding.addParticipantsAction.visibility = GONE
|
||||
binding.clearConversationHistory.visibility = GONE
|
||||
|
||||
if (ConversationUtils.isNoteToSelfConversation(
|
||||
ConversationModel.mapToConversationModel(conversation!!)
|
||||
)
|
||||
) {
|
||||
binding.notificationSettingsView.notificationSettings.visibility = VISIBLE
|
||||
} else {
|
||||
binding.clearConversationHistory.visibility = GONE
|
||||
}
|
||||
}
|
||||
|
||||
if (!isDestroyed) {
|
||||
@ -823,7 +832,6 @@ class ConversationInfoActivity :
|
||||
|
||||
private fun initExpiringMessageOption() {
|
||||
if (conversation!!.isParticipantOwnerOrModerator &&
|
||||
!ConversationUtils.isNoteToSelfConversation(ConversationModel.mapToConversationModel(conversation!!)) &&
|
||||
CapabilitiesUtilNew.hasSpreedFeatureCapability(conversationUser, "message-expiration")
|
||||
) {
|
||||
databaseStorageModule?.setMessageExpiration(conversation!!.messageExpiration)
|
||||
|
@ -50,6 +50,7 @@ import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.widget.SearchView
|
||||
@ -71,8 +72,11 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.nextcloud.android.common.ui.theme.utils.ColorRole
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.account.ServerSelectionActivity
|
||||
import com.nextcloud.talk.account.WebViewLoginActivity
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.activities.CallActivity
|
||||
import com.nextcloud.talk.activities.MainActivity
|
||||
import com.nextcloud.talk.adapters.items.ConversationItem
|
||||
import com.nextcloud.talk.adapters.items.GenericTextHeaderItem
|
||||
import com.nextcloud.talk.adapters.items.LoadMoreResultsItem
|
||||
@ -84,7 +88,7 @@ import com.nextcloud.talk.arbitrarystorage.ArbitraryStorageManager
|
||||
import com.nextcloud.talk.chat.ChatActivity
|
||||
import com.nextcloud.talk.contacts.ContactsActivity
|
||||
import com.nextcloud.talk.data.user.model.User
|
||||
import com.nextcloud.talk.databinding.ControllerConversationsRvBinding
|
||||
import com.nextcloud.talk.databinding.ActivityConversationsBinding
|
||||
import com.nextcloud.talk.events.ConversationsListFetchDataEvent
|
||||
import com.nextcloud.talk.events.EventStatus
|
||||
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
||||
@ -103,12 +107,12 @@ import com.nextcloud.talk.ui.dialog.ConversationsListBottomDialog
|
||||
import com.nextcloud.talk.ui.dialog.FilterConversationFragment
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
import com.nextcloud.talk.utils.ClosedInterfaceImpl
|
||||
import com.nextcloud.talk.utils.FileUtils
|
||||
import com.nextcloud.talk.utils.Mimetype
|
||||
import com.nextcloud.talk.utils.ParticipantPermissions
|
||||
import com.nextcloud.talk.utils.UserIdUtils
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.ADD_ADDITIONAL_ACCOUNT
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_HIDE_SOURCE_ROOM
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_FLAG
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_FORWARD_MSG_TEXT
|
||||
@ -146,7 +150,7 @@ class ConversationsListActivity :
|
||||
FlexibleAdapter.OnItemClickListener,
|
||||
FlexibleAdapter.OnItemLongClickListener {
|
||||
|
||||
private lateinit var binding: ControllerConversationsRvBinding
|
||||
private lateinit var binding: ActivityConversationsBinding
|
||||
|
||||
@Inject
|
||||
lateinit var userManager: UserManager
|
||||
@ -202,7 +206,6 @@ class ConversationsListActivity :
|
||||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
// TODO: replace this when conductor is removed. For now it avoids to load the MainActiviy which has no UI.
|
||||
finishAffinity()
|
||||
}
|
||||
}
|
||||
@ -211,7 +214,7 @@ class ConversationsListActivity :
|
||||
super.onCreate(savedInstanceState)
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
binding = ControllerConversationsRvBinding.inflate(layoutInflater)
|
||||
binding = ActivityConversationsBinding.inflate(layoutInflater)
|
||||
setupActionBar()
|
||||
setContentView(binding.root)
|
||||
setupSystemColors()
|
||||
@ -250,7 +253,6 @@ class ConversationsListActivity :
|
||||
|
||||
showShareToScreen = hasActivityActionSendIntent()
|
||||
|
||||
ClosedInterfaceImpl().setUpPushTokenRegistration()
|
||||
if (!eventBus.isRegistered(this)) {
|
||||
eventBus.register(this)
|
||||
}
|
||||
@ -350,9 +352,7 @@ class ConversationsListActivity :
|
||||
viewThemeUtils.material.themeToolbar(binding.conversationListToolbar)
|
||||
}
|
||||
|
||||
private fun loadUserAvatar(
|
||||
target: Target
|
||||
) {
|
||||
private fun loadUserAvatar(target: Target) {
|
||||
if (currentUser != null) {
|
||||
val url = ApiUtils.getUrlForAvatar(
|
||||
currentUser!!.baseUrl,
|
||||
@ -740,11 +740,20 @@ class ConversationsListActivity :
|
||||
}
|
||||
}
|
||||
|
||||
if (resources!!.getBoolean(R.bool.multiaccount_support)) {
|
||||
dialogBuilder.setNeutralButton(R.string.nc_account_chooser_add_account) { _, _ ->
|
||||
val intent = Intent(this, ServerSelectionActivity::class.java)
|
||||
intent.putExtra(ADD_ADDITIONAL_ACCOUNT, true)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
|
||||
val dialog = dialogBuilder.show()
|
||||
viewThemeUtils.platform.colorTextButtons(
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE),
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -819,10 +828,10 @@ class ConversationsListActivity :
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private fun prepareViews() {
|
||||
layoutManager = SmoothScrollLinearLayoutManager(this)
|
||||
binding?.recyclerView?.layoutManager = layoutManager
|
||||
binding?.recyclerView?.setHasFixedSize(true)
|
||||
binding?.recyclerView?.adapter = adapter
|
||||
binding?.recyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
binding.recyclerView.layoutManager = layoutManager
|
||||
binding.recyclerView.setHasFixedSize(true)
|
||||
binding.recyclerView.adapter = adapter
|
||||
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
|
||||
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
|
||||
super.onScrollStateChanged(recyclerView, newState)
|
||||
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
|
||||
@ -1131,7 +1140,7 @@ class ConversationsListActivity :
|
||||
selectedConversation!!.displayName
|
||||
)
|
||||
}
|
||||
binding?.floatingActionButton?.let {
|
||||
binding.floatingActionButton.let {
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(it.context)
|
||||
.setIcon(viewThemeUtils.dialog.colorMaterialAlertDialogIcon(context, R.drawable.upload))
|
||||
.setTitle(confirmationQuestion)
|
||||
@ -1358,30 +1367,17 @@ class ConversationsListActivity :
|
||||
.setTitle(R.string.nc_dialog_invalid_password)
|
||||
.setMessage(R.string.nc_dialog_reauth_or_delete)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.nc_delete) { _, _ ->
|
||||
val otherUserExists = userManager
|
||||
.scheduleUserForDeletionWithId(currentUser!!.id!!)
|
||||
.blockingGet()
|
||||
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
|
||||
WorkManager.getInstance().enqueue(accountRemovalWork)
|
||||
if (otherUserExists) {
|
||||
finish()
|
||||
startActivity(intent)
|
||||
} else if (!otherUserExists) {
|
||||
Log.d(TAG, "No other users found. AccountRemovalWorker will restart the app.")
|
||||
}
|
||||
.setPositiveButton(R.string.nc_settings_remove_account) { _, _ ->
|
||||
deleteUserAndRestartApp()
|
||||
}
|
||||
.setNegativeButton(R.string.nc_settings_reauthorize) { _, _ ->
|
||||
val intent = Intent(context, WebViewLoginActivity::class.java)
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_BASE_URL, currentUser!!.baseUrl)
|
||||
bundle.putBoolean(BundleKeys.KEY_REAUTHORIZE_ACCOUNT, true)
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
// TODO: show negative button again when conductor is removed
|
||||
// .setNegativeButton(R.string.nc_settings_reauthorize) { _, _ ->
|
||||
// // router.pushController(
|
||||
// // RouterTransaction.with(
|
||||
// // WebViewLoginController(currentUser!!.baseUrl, true)
|
||||
// // )
|
||||
// // .pushChangeHandler(VerticalChangeHandler())
|
||||
// // .popChangeHandler(VerticalChangeHandler())
|
||||
// // )
|
||||
// }
|
||||
|
||||
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
|
||||
val dialog = dialogBuilder.show()
|
||||
@ -1392,6 +1388,50 @@ class ConversationsListActivity :
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private fun deleteUserAndRestartApp() {
|
||||
userManager.scheduleUserForDeletionWithId(currentUser!!.id!!).blockingGet()
|
||||
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
|
||||
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
|
||||
|
||||
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
|
||||
.observeForever { workInfo: WorkInfo ->
|
||||
|
||||
when (workInfo.state) {
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
val text = String.format(
|
||||
context.resources.getString(R.string.nc_deleted_user),
|
||||
currentUser!!.displayName
|
||||
)
|
||||
Toast.makeText(
|
||||
context,
|
||||
text,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
restartApp()
|
||||
}
|
||||
|
||||
WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.resources.getString(R.string.nc_common_error_sorry),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
Log.e(TAG, "something went wrong when deleting user with id " + currentUser!!.userId)
|
||||
restartApp()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun restartApp() {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun showOutdatedClientDialog() {
|
||||
binding.floatingActionButton.let {
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(it.context)
|
||||
@ -1423,11 +1463,20 @@ class ConversationsListActivity :
|
||||
}
|
||||
}
|
||||
|
||||
if (resources!!.getBoolean(R.bool.multiaccount_support)) {
|
||||
dialogBuilder.setNeutralButton(R.string.nc_account_chooser_add_account) { _, _ ->
|
||||
val intent = Intent(this, ServerSelectionActivity::class.java)
|
||||
intent.putExtra(ADD_ADDITIONAL_ACCOUNT, true)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
|
||||
val dialog = dialogBuilder.show()
|
||||
viewThemeUtils.platform.colorTextButtons(
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE),
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1445,23 +1494,31 @@ class ConversationsListActivity :
|
||||
.setTitle(R.string.nc_dialog_maintenance_mode)
|
||||
.setMessage(R.string.nc_dialog_maintenance_mode_description)
|
||||
.setCancelable(false)
|
||||
.setNegativeButton(R.string.nc_settings_remove_account) { _, _ ->
|
||||
deleteUserAndRestartApp()
|
||||
}
|
||||
|
||||
if (resources!!.getBoolean(R.bool.multiaccount_support) && userManager.users.blockingGet().size > 1) {
|
||||
dialogBuilder.setPositiveButton(R.string.nc_switch_account) { _, _ ->
|
||||
val newFragment: DialogFragment = ChooseAccountDialogFragment.newInstance()
|
||||
newFragment.show(supportFragmentManager, ChooseAccountDialogFragment.TAG)
|
||||
}
|
||||
} else {
|
||||
dialogBuilder.setPositiveButton(R.string.nc_close_app) { _, _ ->
|
||||
finishAffinity()
|
||||
finish()
|
||||
}
|
||||
|
||||
if (resources!!.getBoolean(R.bool.multiaccount_support)) {
|
||||
dialogBuilder.setNeutralButton(R.string.nc_account_chooser_add_account) { _, _ ->
|
||||
val intent = Intent(this, ServerSelectionActivity::class.java)
|
||||
intent.putExtra(ADD_ADDITIONAL_ACCOUNT, true)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
|
||||
val dialog = dialogBuilder.show()
|
||||
viewThemeUtils.platform.colorTextButtons(
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE)
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE),
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
@ -1470,55 +1527,41 @@ class ConversationsListActivity :
|
||||
}
|
||||
|
||||
private fun showServerEOLDialog() {
|
||||
binding?.floatingActionButton?.let {
|
||||
binding.floatingActionButton.let {
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(it.context)
|
||||
.setIcon(viewThemeUtils.dialog.colorMaterialAlertDialogIcon(context, R.drawable.ic_warning_white))
|
||||
.setTitle(R.string.nc_settings_server_eol_title)
|
||||
.setMessage(R.string.nc_settings_server_eol)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.nc_settings_remove_account) { _, _ ->
|
||||
val otherUserExists = userManager
|
||||
.scheduleUserForDeletionWithId(currentUser!!.id!!)
|
||||
.blockingGet()
|
||||
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
|
||||
WorkManager.getInstance().enqueue(accountRemovalWork)
|
||||
if (otherUserExists) {
|
||||
finish()
|
||||
startActivity(intent)
|
||||
} else if (!otherUserExists) {
|
||||
restartApp(this)
|
||||
}
|
||||
deleteUserAndRestartApp()
|
||||
}
|
||||
.setNegativeButton(R.string.nc_cancel) { _, _ ->
|
||||
if (userManager.users.blockingGet().isNotEmpty()) {
|
||||
// TODO show SwitchAccount screen again when conductor is removed instead to close app
|
||||
// router.pushController(RouterTransaction.with(SwitchAccountController()))
|
||||
finishAffinity()
|
||||
finish()
|
||||
} else {
|
||||
finishAffinity()
|
||||
finish()
|
||||
}
|
||||
|
||||
if (resources!!.getBoolean(R.bool.multiaccount_support) && userManager.users.blockingGet().size > 1) {
|
||||
dialogBuilder.setNegativeButton(R.string.nc_switch_account) { _, _ ->
|
||||
val newFragment: DialogFragment = ChooseAccountDialogFragment.newInstance()
|
||||
newFragment.show(supportFragmentManager, ChooseAccountDialogFragment.TAG)
|
||||
}
|
||||
}
|
||||
|
||||
if (resources!!.getBoolean(R.bool.multiaccount_support)) {
|
||||
dialogBuilder.setNeutralButton(R.string.nc_account_chooser_add_account) { _, _ ->
|
||||
val intent = Intent(this, ServerSelectionActivity::class.java)
|
||||
intent.putExtra(ADD_ADDITIONAL_ACCOUNT, true)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
viewThemeUtils.dialog.colorMaterialAlertDialogBackground(it.context, dialogBuilder)
|
||||
val dialog = dialogBuilder.show()
|
||||
viewThemeUtils.platform.colorTextButtons(
|
||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE),
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
|
||||
dialog.getButton(AlertDialog.BUTTON_NEGATIVE),
|
||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun restartApp(context: Context) {
|
||||
val packageManager = context.packageManager
|
||||
val intent = packageManager.getLaunchIntentForPackage(context.packageName)
|
||||
val componentName = intent!!.component
|
||||
val mainIntent = Intent.makeRestartActivityTask(componentName)
|
||||
context.startActivity(mainIntent)
|
||||
Runtime.getRuntime().exit(0)
|
||||
}
|
||||
|
||||
private fun deleteConversation(conversation: Conversation) {
|
||||
val data = Data.Builder()
|
||||
data.putLong(
|
||||
@ -1613,10 +1656,10 @@ class ConversationsListActivity :
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "ConvListController"
|
||||
private val TAG = ConversationsListActivity::class.java.simpleName
|
||||
const val UNREAD_BUBBLE_DELAY = 2500
|
||||
const val BOTTOM_SHEET_DELAY: Long = 2500
|
||||
private const val KEY_SEARCH_QUERY = "ContactsController.searchQuery"
|
||||
private const val KEY_SEARCH_QUERY = "ConversationsListActivity.searchQuery"
|
||||
const val SEARCH_DEBOUNCE_INTERVAL_MS = 300
|
||||
const val SEARCH_MIN_CHARS = 2
|
||||
const val HTTP_UNAUTHORIZED = 401
|
||||
|
@ -87,8 +87,10 @@ class RepositoryModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideRemoteFileBrowserItemsRepository(okHttpClient: OkHttpClient, userProvider: CurrentUserProviderNew):
|
||||
RemoteFileBrowserItemsRepository {
|
||||
fun provideRemoteFileBrowserItemsRepository(
|
||||
okHttpClient: OkHttpClient,
|
||||
userProvider: CurrentUserProviderNew
|
||||
): RemoteFileBrowserItemsRepository {
|
||||
return RemoteFileBrowserItemsRepositoryImpl(okHttpClient, userProvider)
|
||||
}
|
||||
|
||||
@ -113,38 +115,41 @@ class RepositoryModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideRequestAssistanceRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew):
|
||||
RequestAssistanceRepository {
|
||||
fun provideRequestAssistanceRepository(
|
||||
ncApi: NcApi,
|
||||
userProvider: CurrentUserProviderNew
|
||||
): RequestAssistanceRepository {
|
||||
return RequestAssistanceRepositoryImpl(ncApi, userProvider)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideOpenConversationsRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew):
|
||||
OpenConversationsRepository {
|
||||
fun provideOpenConversationsRepository(
|
||||
ncApi: NcApi,
|
||||
userProvider: CurrentUserProviderNew
|
||||
): OpenConversationsRepository {
|
||||
return OpenConversationsRepositoryImpl(ncApi, userProvider)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun translateRepository(ncApi: NcApi):
|
||||
TranslateRepository {
|
||||
fun translateRepository(ncApi: NcApi): TranslateRepository {
|
||||
return TranslateRepositoryImpl(ncApi)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideChatRepository(ncApi: NcApi):
|
||||
ChatRepository {
|
||||
fun provideChatRepository(ncApi: NcApi): ChatRepository {
|
||||
return ChatRepositoryImpl(ncApi)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideConversationInfoEditRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew):
|
||||
ConversationInfoEditRepository {
|
||||
fun provideConversationInfoEditRepository(
|
||||
ncApi: NcApi,
|
||||
userProvider: CurrentUserProviderNew
|
||||
): ConversationInfoEditRepository {
|
||||
return ConversationInfoEditRepositoryImpl(ncApi, userProvider)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideConversationRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew):
|
||||
ConversationRepository {
|
||||
fun provideConversationRepository(ncApi: NcApi, userProvider: CurrentUserProviderNew): ConversationRepository {
|
||||
return ConversationRepositoryImpl(ncApi, userProvider)
|
||||
}
|
||||
}
|
||||
|
@ -64,12 +64,12 @@ abstract class TalkDatabase : RoomDatabase() {
|
||||
const val TAG = "TalkDatabase"
|
||||
|
||||
@Volatile
|
||||
private var INSTANCE: TalkDatabase? = null
|
||||
private var instance: TalkDatabase? = null
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance(context: Context, appPreferences: AppPreferences): TalkDatabase =
|
||||
INSTANCE ?: synchronized(this) {
|
||||
INSTANCE ?: build(context, appPreferences).also { INSTANCE = it }
|
||||
instance ?: synchronized(this) {
|
||||
instance ?: build(context, appPreferences).also { instance = it }
|
||||
}
|
||||
|
||||
private fun build(context: Context, appPreferences: AppPreferences): TalkDatabase {
|
||||
|
@ -24,10 +24,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.activities
|
||||
package com.nextcloud.talk.fullscreenfile
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
@ -35,7 +33,6 @@ import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.view.ViewCompat
|
||||
@ -44,28 +41,25 @@ import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import autodagger.AutoInjector
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.nextcloud.talk.BuildConfig
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.databinding.ActivityFullScreenImageBinding
|
||||
import com.nextcloud.talk.jobs.SaveFileToStorageWorker
|
||||
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
||||
import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment
|
||||
import com.nextcloud.talk.utils.BitmapShrinker
|
||||
import com.nextcloud.talk.utils.Mimetype.IMAGE_PREFIX_GENERIC
|
||||
import pl.droidsonroids.gif.GifDrawable
|
||||
import java.io.File
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class FullScreenImageActivity : AppCompatActivity() {
|
||||
lateinit var binding: ActivityFullScreenImageBinding
|
||||
private lateinit var windowInsetsController: WindowInsetsControllerCompat
|
||||
private lateinit var path: String
|
||||
private var showFullscreen = false
|
||||
lateinit var viewThemeUtils: ViewThemeUtils
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_preview, menu)
|
||||
@ -98,7 +92,13 @@ class FullScreenImageActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
R.id.save -> {
|
||||
showWarningDialog()
|
||||
val saveFragment: DialogFragment = SaveToStorageDialogFragment.newInstance(
|
||||
intent.getStringExtra("FILE_NAME").toString()
|
||||
)
|
||||
saveFragment.show(
|
||||
supportFragmentManager,
|
||||
SaveToStorageDialogFragment.TAG
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
@ -108,24 +108,9 @@ class FullScreenImageActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun showWarningDialog() {
|
||||
val builder = AlertDialog.Builder(this)
|
||||
builder.setTitle(R.string.nc_dialog_save_to_storage_title)
|
||||
builder.setMessage(R.string.nc_dialog_save_to_storage_content)
|
||||
builder.setPositiveButton(R.string.nc_dialog_save_to_storage_yes) { dialog: DialogInterface, which: Int ->
|
||||
val fileName = intent.getStringExtra("FILE_NAME").toString()
|
||||
saveImageToStorage(fileName)
|
||||
dialog.dismiss()
|
||||
}
|
||||
builder.setNegativeButton(R.string.nc_dialog_save_to_storage_no) { dialog: DialogInterface, which: Int ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
val dialog = builder.create()
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
NextcloudTalkApplication.sharedApplication!!.componentApplication.inject(this)
|
||||
|
||||
binding = ActivityFullScreenImageBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
@ -222,38 +207,6 @@ class FullScreenImageActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
private fun saveImageToStorage(
|
||||
fileName: String
|
||||
) {
|
||||
val sourceFilePath = applicationContext.cacheDir.path
|
||||
|
||||
val workers = WorkManager.getInstance(this).getWorkInfosByTag(fileName)
|
||||
try {
|
||||
for (workInfo in workers.get()) {
|
||||
if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) {
|
||||
return
|
||||
}
|
||||
}
|
||||
} catch (e: ExecutionException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
} catch (e: InterruptedException) {
|
||||
Log.e(TAG, "Error when checking if worker already exists", e)
|
||||
}
|
||||
|
||||
val data: Data = Data.Builder()
|
||||
.putString(SaveFileToStorageWorker.KEY_FILE_NAME, fileName)
|
||||
.putString(SaveFileToStorageWorker.KEY_SOURCE_FILE_PATH, "$sourceFilePath/$fileName")
|
||||
.build()
|
||||
|
||||
val saveWorker: OneTimeWorkRequest = OneTimeWorkRequest.Builder(SaveFileToStorageWorker::class.java)
|
||||
.setInputData(data)
|
||||
.addTag(fileName)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance().enqueue(saveWorker)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "FullScreenImageActivity"
|
||||
private const val HUNDRED_MB = 100 * 1024 * 1024
|
@ -24,7 +24,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.activities
|
||||
package com.nextcloud.talk.fullscreenfile
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
@ -43,6 +43,7 @@ import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.core.view.marginBottom
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
@ -53,6 +54,7 @@ import com.nextcloud.talk.BuildConfig
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.databinding.ActivityFullScreenMediaBinding
|
||||
import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment
|
||||
import com.nextcloud.talk.utils.Mimetype.VIDEO_PREFIX_GENERIC
|
||||
import java.io.File
|
||||
|
||||
@ -78,6 +80,7 @@ class FullScreenMediaActivity : AppCompatActivity() {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
true
|
||||
}
|
||||
|
||||
R.id.share -> {
|
||||
val shareUri = FileProvider.getUriForFile(
|
||||
this,
|
||||
@ -95,6 +98,18 @@ class FullScreenMediaActivity : AppCompatActivity() {
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
R.id.save -> {
|
||||
val saveFragment: DialogFragment = SaveToStorageDialogFragment.newInstance(
|
||||
intent.getStringExtra("FILE_NAME").toString()
|
||||
)
|
||||
saveFragment.show(
|
||||
supportFragmentManager,
|
||||
SaveToStorageDialogFragment.TAG
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
else -> {
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
@ -22,7 +22,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.activities
|
||||
package com.nextcloud.talk.fullscreenfile
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
@ -31,11 +31,13 @@ import android.view.MenuItem
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import autodagger.AutoInjector
|
||||
import com.nextcloud.talk.BuildConfig
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.databinding.ActivityFullScreenTextBinding
|
||||
import com.nextcloud.talk.ui.dialog.SaveToStorageDialogFragment
|
||||
import com.nextcloud.talk.ui.theme.ViewThemeUtils
|
||||
import com.nextcloud.talk.utils.DisplayUtils
|
||||
import com.nextcloud.talk.utils.Mimetype.TEXT_PREFIX_GENERIC
|
||||
@ -58,27 +60,44 @@ class FullScreenTextViewerActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return if (item.itemId == android.R.id.home) {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
true
|
||||
} else if (item.itemId == R.id.share) {
|
||||
val shareUri = FileProvider.getUriForFile(
|
||||
this,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
File(path)
|
||||
)
|
||||
|
||||
val shareIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_STREAM, shareUri)
|
||||
type = TEXT_PREFIX_GENERIC
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
return when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
true
|
||||
}
|
||||
startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.send_to)))
|
||||
|
||||
true
|
||||
} else {
|
||||
super.onOptionsItemSelected(item)
|
||||
R.id.share -> {
|
||||
val shareUri = FileProvider.getUriForFile(
|
||||
this,
|
||||
BuildConfig.APPLICATION_ID,
|
||||
File(path)
|
||||
)
|
||||
|
||||
val shareIntent: Intent = Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
putExtra(Intent.EXTRA_STREAM, shareUri)
|
||||
type = TEXT_PREFIX_GENERIC
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}
|
||||
startActivity(Intent.createChooser(shareIntent, resources.getText(R.string.send_to)))
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
R.id.save -> {
|
||||
val saveFragment: DialogFragment = SaveToStorageDialogFragment.newInstance(
|
||||
intent.getStringExtra("FILE_NAME").toString()
|
||||
)
|
||||
saveFragment.show(
|
||||
supportFragmentManager,
|
||||
SaveToStorageDialogFragment.TAG
|
||||
)
|
||||
true
|
||||
}
|
||||
|
||||
else -> {
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,10 +23,7 @@
|
||||
package com.nextcloud.talk.jobs;
|
||||
|
||||
import android.app.NotificationManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
@ -129,6 +126,7 @@ public class AccountRemovalWorker extends Worker {
|
||||
@Override
|
||||
public void onError(@io.reactivex.annotations.NonNull Throwable e) {
|
||||
Log.e(TAG, "error while trying to unregister Device For Notifications", e);
|
||||
initiateUserDeletion(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -137,7 +135,7 @@ public class AccountRemovalWorker extends Worker {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
deleteUser(user);
|
||||
initiateUserDeletion(user);
|
||||
}
|
||||
}
|
||||
|
||||
@ -172,15 +170,13 @@ public class AccountRemovalWorker extends Worker {
|
||||
}
|
||||
}
|
||||
|
||||
if (user.getId() != null) {
|
||||
WebSocketConnectionHelper.deleteExternalSignalingInstanceForUserEntity(user.getId());
|
||||
}
|
||||
deleteAllEntriesForAccountIdentifier(user);
|
||||
initiateUserDeletion(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable e) {
|
||||
Log.e(TAG, "error while trying to unregister Device For Notification With Proxy", e);
|
||||
initiateUserDeletion(user);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -190,8 +186,10 @@ public class AccountRemovalWorker extends Worker {
|
||||
});
|
||||
}
|
||||
|
||||
private void deleteAllEntriesForAccountIdentifier(User user) {
|
||||
private void initiateUserDeletion(User user) {
|
||||
if (user.getId() != null) {
|
||||
WebSocketConnectionHelper.deleteExternalSignalingInstanceForUserEntity(user.getId());
|
||||
|
||||
try {
|
||||
arbitraryStorageManager.deleteAllEntriesForAccountIdentifier(user.getId());
|
||||
deleteUser(user);
|
||||
@ -211,17 +209,5 @@ public class AccountRemovalWorker extends Worker {
|
||||
Log.e(TAG, "error while trying to delete user", e);
|
||||
}
|
||||
}
|
||||
if (userManager.getUsers().blockingGet().isEmpty()) {
|
||||
restartApp(getApplicationContext());
|
||||
}
|
||||
}
|
||||
|
||||
public static void restartApp(Context context) {
|
||||
PackageManager packageManager = context.getPackageManager();
|
||||
Intent intent = packageManager.getLaunchIntentForPackage(context.getPackageName());
|
||||
ComponentName componentName = intent.getComponent();
|
||||
Intent mainIntent = Intent.makeRestartActivityTask(componentName);
|
||||
context.startActivity(mainIntent);
|
||||
Runtime.getRuntime().exit(0);
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ import android.service.notification.StatusBarNotification
|
||||
import android.text.TextUtils
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
@ -56,7 +56,6 @@ import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import autodagger.AutoInjector
|
||||
import com.bluelinelabs.logansquare.LoganSquare
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.nextcloud.talk.BuildConfig
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.MainActivity
|
||||
@ -329,11 +328,7 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
||||
Log.e(TAG, "Failed to get NC notification", e)
|
||||
if (BuildConfig.DEBUG) {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
Snackbar.make(
|
||||
View(applicationContext),
|
||||
"Failed to get NC notification",
|
||||
Snackbar.LENGTH_LONG
|
||||
).show()
|
||||
Toast.makeText(context, "Failed to get NC notification", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -857,6 +852,10 @@ class NotificationWorker(context: Context, workerParams: WorkerParameters) : Wor
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
Log.e(TAG, "Error in getPeersForCall", e)
|
||||
if (isCallNotificationVisible) {
|
||||
showMissedCallNotification()
|
||||
}
|
||||
removeNotification(pushMessage.timestamp.toInt())
|
||||
}
|
||||
|
||||
override fun onComplete() {
|
||||
|
@ -64,7 +64,7 @@ public class PushRegistrationWorker extends Worker {
|
||||
@Override
|
||||
public Result doWork() {
|
||||
NextcloudTalkApplication.Companion.getSharedApplication().getComponentApplication().inject(this);
|
||||
if(new ClosedInterfaceImpl().isGooglePlayServicesAvailable()){
|
||||
if (new ClosedInterfaceImpl().isGooglePlayServicesAvailable()) {
|
||||
Data data = getInputData();
|
||||
String origin = data.getString("origin");
|
||||
Log.d(TAG, "PushRegistrationWorker called via " + origin);
|
||||
|
@ -1,10 +1,10 @@
|
||||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Andy Scherzinger
|
||||
* @author Fariba Khandani
|
||||
* @author Marcel Hibbe
|
||||
* Copyright (C) 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
|
||||
* Copyright (C) 2023 Fariba Khandani <khandani@winworker.de>
|
||||
* Copyright (C) 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@ -25,14 +25,23 @@ package com.nextcloud.talk.jobs
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.media.MediaScannerConnection
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.provider.MediaStore
|
||||
import android.provider.MediaStore.Files.FileColumns
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import autodagger.AutoInjector
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.utils.Mimetype.AUDIO_PREFIX
|
||||
import com.nextcloud.talk.utils.Mimetype.IMAGE_PREFIX
|
||||
import com.nextcloud.talk.utils.Mimetype.VIDEO_PREFIX
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
@ -50,16 +59,22 @@ class SaveFileToStorageWorker(val context: Context, workerParameters: WorkerPara
|
||||
val contentResolver = context.contentResolver
|
||||
val mimeType = URLConnection.guessContentTypeFromName(cacheFile.name)
|
||||
|
||||
val appName = applicationContext.resources!!.getString(R.string.nc_app_product_name)
|
||||
|
||||
val values = ContentValues().apply {
|
||||
if (mimeType.startsWith(IMAGE_PREFIX) || mimeType.startsWith(VIDEO_PREFIX)) {
|
||||
put(FileColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM + "/" + appName)
|
||||
}
|
||||
put(FileColumns.DISPLAY_NAME, cacheFile.name)
|
||||
put(FileColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||
|
||||
if (mimeType != null) {
|
||||
put(FileColumns.MIME_TYPE, URLConnection.guessContentTypeFromName(cacheFile.name))
|
||||
put(FileColumns.MIME_TYPE, mimeType)
|
||||
}
|
||||
}
|
||||
|
||||
val collection = MediaStore.Files.getContentUri("external")
|
||||
val uri = contentResolver.insert(collection, values)
|
||||
val collectionUri = getUriByType(mimeType)
|
||||
|
||||
val uri = contentResolver.insert(collectionUri, values)
|
||||
|
||||
uri?.let { fileUri ->
|
||||
try {
|
||||
@ -79,16 +94,53 @@ class SaveFileToStorageWorker(val context: Context, workerParameters: WorkerPara
|
||||
// Notify the media scanner about the new file
|
||||
MediaScannerConnection.scanFile(context, arrayOf(cacheFile.absolutePath), null, null)
|
||||
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.resources.getString(R.string.nc_save_success),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
return Result.success()
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Something went wrong when trying to save file to internal storage", e)
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.resources.getString(R.string.nc_common_error_sorry),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
return Result.failure()
|
||||
} catch (e: NullPointerException) {
|
||||
Log.e(TAG, "Something went wrong when trying to save file to internal storage", e)
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.resources.getString(R.string.nc_common_error_sorry),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
return Result.failure()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUriByType(mimeType: String): Uri {
|
||||
return when {
|
||||
mimeType.startsWith(VIDEO_PREFIX) -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||
mimeType.startsWith(AUDIO_PREFIX) -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||
mimeType.startsWith(IMAGE_PREFIX) -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||
else -> if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
|
||||
Uri.fromFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS))
|
||||
} else {
|
||||
MediaStore.Downloads.EXTERNAL_CONTENT_URI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = SaveFileToStorageWorker::class.java.simpleName
|
||||
const val KEY_FILE_NAME = "KEY_FILE_NAME"
|
||||
|
@ -93,12 +93,7 @@ class ShareOperationWorker(context: Context, workerParams: WorkerParameters) : W
|
||||
companion object {
|
||||
private val TAG = ShareOperationWorker::class.simpleName
|
||||
|
||||
fun shareFile(
|
||||
roomToken: String?,
|
||||
currentUser: User,
|
||||
remotePath: String,
|
||||
metaData: String?
|
||||
) {
|
||||
fun shareFile(roomToken: String?, currentUser: User, remotePath: String, metaData: String?) {
|
||||
val paths: MutableList<String> = ArrayList()
|
||||
paths.add(remotePath)
|
||||
|
||||
|
@ -186,9 +186,7 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
|
||||
return remotePath
|
||||
}
|
||||
|
||||
override fun onTransferProgress(
|
||||
percentage: Int
|
||||
) {
|
||||
override fun onTransferProgress(percentage: Int) {
|
||||
notification = mBuilder!!
|
||||
.setProgress(HUNDRED_PERCENT, percentage, false)
|
||||
.setContentText(getNotificationContentText(percentage))
|
||||
@ -322,12 +320,7 @@ class UploadAndShareFilesWorker(val context: Context, workerParameters: WorkerPa
|
||||
}
|
||||
}
|
||||
|
||||
fun upload(
|
||||
fileUri: String,
|
||||
roomToken: String,
|
||||
conversationName: String,
|
||||
metaData: String?
|
||||
) {
|
||||
fun upload(fileUri: String, roomToken: String, conversationName: String, metaData: String?) {
|
||||
val data: Data = Data.Builder()
|
||||
.putString(DEVICE_SOURCE_FILE, fileUri)
|
||||
.putString(ROOM_TOKEN, roomToken)
|
||||
|
@ -121,7 +121,7 @@ class GeocodingActivity :
|
||||
if (viewModel.getQuery().isNotEmpty() && adapter.itemCount == 0) {
|
||||
viewModel.searchLocation()
|
||||
} else {
|
||||
Log.e(TAG, "search string that was passed to GeocodingController was null or empty")
|
||||
Log.e(TAG, "search string that was passed to GeocodingActivity was null or empty")
|
||||
}
|
||||
adapter.setOnItemClickListener(object : GeocodingAdapter.OnItemClickListener {
|
||||
override fun onItemClick(position: Int) {
|
||||
|
@ -335,41 +335,42 @@ class LocationPickerActivity :
|
||||
)
|
||||
}
|
||||
|
||||
private fun delayedMapListener() = DelayedMapListener(
|
||||
object : MapListener {
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
override fun onScroll(paramScrollEvent: ScrollEvent): Boolean {
|
||||
try {
|
||||
when {
|
||||
moveToCurrentLocation -> {
|
||||
setLocationDescription(isGpsLocation = true, isGeocodedResult = false)
|
||||
moveToCurrentLocation = false
|
||||
}
|
||||
private fun delayedMapListener() =
|
||||
DelayedMapListener(
|
||||
object : MapListener {
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
override fun onScroll(paramScrollEvent: ScrollEvent): Boolean {
|
||||
try {
|
||||
when {
|
||||
moveToCurrentLocation -> {
|
||||
setLocationDescription(isGpsLocation = true, isGeocodedResult = false)
|
||||
moveToCurrentLocation = false
|
||||
}
|
||||
|
||||
geocodingResult != null -> {
|
||||
binding.shareLocation.isClickable = true
|
||||
setLocationDescription(isGpsLocation = false, isGeocodedResult = true)
|
||||
geocodingResult = null
|
||||
}
|
||||
geocodingResult != null -> {
|
||||
binding.shareLocation.isClickable = true
|
||||
setLocationDescription(isGpsLocation = false, isGeocodedResult = true)
|
||||
geocodingResult = null
|
||||
}
|
||||
|
||||
else -> {
|
||||
binding.shareLocation.isClickable = true
|
||||
setLocationDescription(isGpsLocation = false, isGeocodedResult = false)
|
||||
else -> {
|
||||
binding.shareLocation.isClickable = true
|
||||
setLocationDescription(isGpsLocation = false, isGeocodedResult = false)
|
||||
}
|
||||
}
|
||||
} catch (e: NullPointerException) {
|
||||
Log.d(TAG, "UI already closed")
|
||||
}
|
||||
} catch (e: NullPointerException) {
|
||||
Log.d(TAG, "UI already closed")
|
||||
|
||||
readyToShareLocation = true
|
||||
return true
|
||||
}
|
||||
|
||||
readyToShareLocation = true
|
||||
return true
|
||||
override fun onZoom(event: ZoomEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onZoom(event: ZoomEvent): Boolean {
|
||||
return false
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun requestLocationUpdates() {
|
||||
@ -524,11 +525,7 @@ class LocationPickerActivity :
|
||||
)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<out String>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
|
||||
fun areAllGranted(grantResults: IntArray): Boolean {
|
||||
|
@ -46,9 +46,7 @@ class ConversationModel(
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun mapToConversationModel(
|
||||
conversation: Conversation
|
||||
): ConversationModel {
|
||||
fun mapToConversationModel(conversation: Conversation): ConversationModel {
|
||||
return ConversationModel(
|
||||
roomId = conversation.roomId,
|
||||
token = conversation.token,
|
||||
@ -113,7 +111,13 @@ enum class ConversationType {
|
||||
}
|
||||
|
||||
enum class ParticipantType {
|
||||
DUMMY, OWNER, MODERATOR, USER, GUEST, USER_FOLLOWING_LINK, GUEST_MODERATOR
|
||||
DUMMY,
|
||||
OWNER,
|
||||
MODERATOR,
|
||||
USER,
|
||||
GUEST,
|
||||
USER_FOLLOWING_LINK,
|
||||
GUEST_MODERATOR
|
||||
}
|
||||
|
||||
enum class ObjectType {
|
||||
@ -124,13 +128,18 @@ enum class ObjectType {
|
||||
}
|
||||
|
||||
enum class NotificationLevel {
|
||||
DEFAULT, ALWAYS, MENTION, NEVER
|
||||
DEFAULT,
|
||||
ALWAYS,
|
||||
MENTION,
|
||||
NEVER
|
||||
}
|
||||
|
||||
enum class ConversationReadOnlyState {
|
||||
CONVERSATION_READ_WRITE, CONVERSATION_READ_ONLY
|
||||
CONVERSATION_READ_WRITE,
|
||||
CONVERSATION_READ_ONLY
|
||||
}
|
||||
|
||||
enum class LobbyState {
|
||||
LOBBY_STATE_ALL_PARTICIPANTS, LOBBY_STATE_MODERATORS_ONLY
|
||||
LOBBY_STATE_ALL_PARTICIPANTS,
|
||||
LOBBY_STATE_MODERATORS_ONLY
|
||||
}
|
||||
|
@ -285,6 +285,7 @@ data class ChatMessage(
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
val lastMessageDisplayText: String
|
||||
get() {
|
||||
if (getCalculateMessageType() == MessageType.REGULAR_TEXT_MESSAGE ||
|
||||
@ -373,11 +374,12 @@ data class ChatMessage(
|
||||
return ""
|
||||
}
|
||||
|
||||
private fun getNullsafeActorDisplayName() = if (!TextUtils.isEmpty(actorDisplayName)) {
|
||||
actorDisplayName
|
||||
} else {
|
||||
sharedApplication!!.getString(R.string.nc_guest)
|
||||
}
|
||||
private fun getNullsafeActorDisplayName() =
|
||||
if (!TextUtils.isEmpty(actorDisplayName)) {
|
||||
actorDisplayName
|
||||
} else {
|
||||
sharedApplication!!.getString(R.string.nc_guest)
|
||||
}
|
||||
|
||||
override fun getUser(): IUser {
|
||||
return object : IUser {
|
||||
@ -470,7 +472,8 @@ data class ChatMessage(
|
||||
* see https://nextcloud-talk.readthedocs.io/en/latest/chat/#system-messages
|
||||
*/
|
||||
enum class SystemMessageType {
|
||||
DUMMY, CONVERSATION_CREATED,
|
||||
DUMMY,
|
||||
CONVERSATION_CREATED,
|
||||
CONVERSATION_RENAMED,
|
||||
DESCRIPTION_REMOVED,
|
||||
DESCRIPTION_SET,
|
||||
@ -504,7 +507,8 @@ data class ChatMessage(
|
||||
GUEST_MODERATOR_PROMOTED,
|
||||
GUEST_MODERATOR_DEMOTED,
|
||||
MESSAGE_DELETED,
|
||||
FILE_SHARED, OBJECT_SHARED,
|
||||
FILE_SHARED,
|
||||
OBJECT_SHARED,
|
||||
MATTERBRIDGE_CONFIG_ADDED,
|
||||
MATTERBRIDGE_CONFIG_EDITED,
|
||||
MATTERBRIDGE_CONFIG_REMOVED,
|
||||
|
@ -26,8 +26,10 @@ package com.nextcloud.talk.models.json.chat
|
||||
|
||||
class ChatUtils {
|
||||
companion object {
|
||||
fun getParsedMessage(message: String?, messageParameters: HashMap<String?, HashMap<String?, String?>>?):
|
||||
String? {
|
||||
fun getParsedMessage(
|
||||
message: String?,
|
||||
messageParameters: HashMap<String?, HashMap<String?, String?>>?
|
||||
): String? {
|
||||
if (messageParameters != null && messageParameters.size > 0) {
|
||||
return parse(messageParameters, message)
|
||||
}
|
||||
@ -35,10 +37,7 @@ class ChatUtils {
|
||||
}
|
||||
|
||||
@Suppress("Detekt.ComplexMethod")
|
||||
private fun parse(
|
||||
messageParameters: HashMap<String?, HashMap<String?, String?>>,
|
||||
message: String?
|
||||
): String? {
|
||||
private fun parse(messageParameters: HashMap<String?, HashMap<String?, String?>>, message: String?): String? {
|
||||
var resultMessage = message
|
||||
for (key in messageParameters.keys) {
|
||||
val individualHashMap = messageParameters[key]
|
||||
|
@ -20,5 +20,7 @@
|
||||
package com.nextcloud.talk.models.json.chat
|
||||
|
||||
enum class ReadStatus {
|
||||
NONE, SENT, READ
|
||||
NONE,
|
||||
SENT,
|
||||
READ
|
||||
}
|
||||
|
@ -229,15 +229,20 @@ data class Conversation(
|
||||
}
|
||||
|
||||
enum class NotificationLevel {
|
||||
DEFAULT, ALWAYS, MENTION, NEVER
|
||||
DEFAULT,
|
||||
ALWAYS,
|
||||
MENTION,
|
||||
NEVER
|
||||
}
|
||||
|
||||
enum class LobbyState {
|
||||
LOBBY_STATE_ALL_PARTICIPANTS, LOBBY_STATE_MODERATORS_ONLY
|
||||
LOBBY_STATE_ALL_PARTICIPANTS,
|
||||
LOBBY_STATE_MODERATORS_ONLY
|
||||
}
|
||||
|
||||
enum class ConversationReadOnlyState {
|
||||
CONVERSATION_READ_WRITE, CONVERSATION_READ_ONLY
|
||||
CONVERSATION_READ_WRITE,
|
||||
CONVERSATION_READ_ONLY
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
|
@ -124,11 +124,22 @@ data class Participant(
|
||||
}
|
||||
|
||||
enum class ActorType {
|
||||
DUMMY, EMAILS, GROUPS, GUESTS, USERS, CIRCLES
|
||||
DUMMY,
|
||||
EMAILS,
|
||||
GROUPS,
|
||||
GUESTS,
|
||||
USERS,
|
||||
CIRCLES
|
||||
}
|
||||
|
||||
enum class ParticipantType {
|
||||
DUMMY, OWNER, MODERATOR, USER, GUEST, USER_FOLLOWING_LINK, GUEST_MODERATOR
|
||||
DUMMY,
|
||||
OWNER,
|
||||
MODERATOR,
|
||||
USER,
|
||||
GUEST,
|
||||
USER_FOLLOWING_LINK,
|
||||
GUEST_MODERATOR
|
||||
}
|
||||
|
||||
object InCallFlags {
|
||||
|
@ -41,6 +41,8 @@ data class ReactionVoter(
|
||||
constructor() : this(null, null, null, 0)
|
||||
|
||||
enum class ReactionActorType {
|
||||
DUMMY, GUESTS, USERS
|
||||
DUMMY,
|
||||
GUESTS,
|
||||
USERS
|
||||
}
|
||||
}
|
||||
|
@ -42,9 +42,7 @@ class OpenConversationsRepositoryImpl(private val ncApi: NcApi, currentUserProvi
|
||||
).map { mapToOpenConversationsModel(it.ocs?.data!!) }
|
||||
}
|
||||
|
||||
private fun mapToOpenConversationsModel(
|
||||
conversations: List<Conversation>
|
||||
): OpenConversationsModel {
|
||||
private fun mapToOpenConversationsModel(conversations: List<Conversation>): OpenConversationsModel {
|
||||
return OpenConversationsModel(
|
||||
conversations.map { conversation ->
|
||||
OpenConversation(
|
||||
|
@ -75,20 +75,19 @@ class PollCreateOptionViewHolder(
|
||||
private fun getTextWatcher(
|
||||
pollCreateOptionItem: PollCreateOptionItem,
|
||||
itemsListener: PollCreateOptionsItemListener
|
||||
) =
|
||||
object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onTextChanged(option: CharSequence, start: Int, before: Int, count: Int) {
|
||||
pollCreateOptionItem.pollOption = option.toString()
|
||||
|
||||
itemsListener.onOptionsItemTextChanged(pollCreateOptionItem)
|
||||
}
|
||||
) = object : TextWatcher {
|
||||
override fun afterTextChanged(s: Editable) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {
|
||||
// unused atm
|
||||
}
|
||||
|
||||
override fun onTextChanged(option: CharSequence, start: Int, before: Int, count: Int) {
|
||||
pollCreateOptionItem.pollOption = option.toString()
|
||||
|
||||
itemsListener.onOptionsItemTextChanged(pollCreateOptionItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,11 +49,7 @@ class PollLoadingFragment : Fragment() {
|
||||
fragmentHeight = arguments?.getInt(KEY_FRAGMENT_HEIGHT)!!
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = DialogPollLoadingBinding.inflate(inflater, container, false)
|
||||
binding.root.layoutParams.height = fragmentHeight
|
||||
viewThemeUtils.platform.colorCircularProgressBar(binding.pollLoadingProgressbar, ColorRole.PRIMARY)
|
||||
@ -65,9 +61,7 @@ class PollLoadingFragment : Fragment() {
|
||||
private const val KEY_FRAGMENT_HEIGHT = "keyFragmentHeight"
|
||||
|
||||
@JvmStatic
|
||||
fun newInstance(
|
||||
fragmentHeight: Int
|
||||
): PollLoadingFragment {
|
||||
fun newInstance(fragmentHeight: Int): PollLoadingFragment {
|
||||
val args = bundleOf(
|
||||
KEY_FRAGMENT_HEIGHT to fragmentHeight
|
||||
)
|
||||
|
@ -121,7 +121,7 @@ class PollMainDialogFragment : DialogFragment() {
|
||||
|
||||
private fun showLoadingScreen() {
|
||||
binding.root.post {
|
||||
run() {
|
||||
run {
|
||||
val fragmentHeight = binding.messagePollContentFragment.measuredHeight
|
||||
|
||||
val contentFragment = PollLoadingFragment.newInstance(fragmentHeight)
|
||||
|
@ -65,11 +65,7 @@ class PollResultsFragment : Fragment(), PollResultItemClickListener {
|
||||
parentViewModel = ViewModelProvider(requireParentFragment(), viewModelFactory)[PollMainViewModel::class.java]
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = DialogPollResultsBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
@ -69,11 +69,7 @@ class PollVoteFragment : Fragment() {
|
||||
parentViewModel = ViewModelProvider(requireParentFragment(), viewModelFactory)[PollMainViewModel::class.java]
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
binding = DialogPollVoteBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
@ -41,8 +41,8 @@ class PollResultsViewModel @Inject constructor() : ViewModel() {
|
||||
val poll: Poll?
|
||||
get() = _poll
|
||||
|
||||
private var _itemsOverviewList: ArrayList<PollResultItem> = ArrayList()
|
||||
private var _itemsDetailsList: ArrayList<PollResultItem> = ArrayList()
|
||||
private var itemsOverviewList: ArrayList<PollResultItem> = ArrayList()
|
||||
private var itemsDetailsList: ArrayList<PollResultItem> = ArrayList()
|
||||
|
||||
private var _items: MutableLiveData<ArrayList<PollResultItem>?> = MutableLiveData<ArrayList<PollResultItem>?>()
|
||||
val items: MutableLiveData<ArrayList<PollResultItem>?>
|
||||
@ -77,23 +77,23 @@ class PollResultsViewModel @Inject constructor() : ViewModel() {
|
||||
optionsPercent,
|
||||
isOptionSelfVoted(poll, index)
|
||||
)
|
||||
_itemsOverviewList.add(pollResultHeaderItem)
|
||||
_itemsDetailsList.add(pollResultHeaderItem)
|
||||
itemsOverviewList.add(pollResultHeaderItem)
|
||||
itemsDetailsList.add(pollResultHeaderItem)
|
||||
|
||||
val voters = poll.details?.filter { it.optionId == index }
|
||||
|
||||
if (!voters.isNullOrEmpty()) {
|
||||
_itemsOverviewList.add(PollResultVotersOverviewItem(voters))
|
||||
itemsOverviewList.add(PollResultVotersOverviewItem(voters))
|
||||
}
|
||||
|
||||
if (!voters.isNullOrEmpty()) {
|
||||
voters.forEach {
|
||||
_itemsDetailsList.add(PollResultVoterItem(it))
|
||||
itemsDetailsList.add(PollResultVoterItem(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_items.value = _itemsOverviewList
|
||||
_items.value = itemsOverviewList
|
||||
}
|
||||
|
||||
private fun getVotersAmountForOption(poll: Poll, index: Int): Int {
|
||||
@ -114,10 +114,10 @@ class PollResultsViewModel @Inject constructor() : ViewModel() {
|
||||
}
|
||||
|
||||
fun toggleDetails() {
|
||||
if (_items.value?.containsAll(_itemsDetailsList) == true) {
|
||||
_items.value = _itemsOverviewList
|
||||
if (_items.value?.containsAll(itemsDetailsList) == true) {
|
||||
_items.value = itemsOverviewList
|
||||
} else {
|
||||
_items.value = _itemsDetailsList
|
||||
_items.value = itemsDetailsList
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -608,7 +608,7 @@ class ProfileActivity : BaseActivity() {
|
||||
class UserInfoAdapter(
|
||||
displayList: List<UserInfoDetailsItem>?,
|
||||
private val viewThemeUtils: ViewThemeUtils,
|
||||
private val controller: ProfileActivity
|
||||
private val profileActivity: ProfileActivity
|
||||
) : RecyclerView.Adapter<UserInfoAdapter.ViewHolder>() {
|
||||
var displayList: List<UserInfoDetailsItem>?
|
||||
var filteredDisplayList: MutableList<UserInfoDetailsItem> = LinkedList()
|
||||
@ -643,7 +643,7 @@ class ProfileActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
val item: UserInfoDetailsItem = if (controller.edit) {
|
||||
val item: UserInfoDetailsItem = if (profileActivity.edit) {
|
||||
displayList!![position]
|
||||
} else {
|
||||
filteredDisplayList[position]
|
||||
@ -656,11 +656,11 @@ class ProfileActivity : BaseActivity() {
|
||||
|
||||
holder.binding.icon.contentDescription = item.hint
|
||||
viewThemeUtils.platform.colorImageView(holder.binding.icon, ColorRole.PRIMARY)
|
||||
if (!TextUtils.isEmpty(item.text) || controller.edit) {
|
||||
if (!TextUtils.isEmpty(item.text) || profileActivity.edit) {
|
||||
holder.binding.userInfoDetailContainer.visibility = View.VISIBLE
|
||||
controller.viewThemeUtils.material.colorTextInputLayout(holder.binding.userInfoInputLayout)
|
||||
if (controller.edit &&
|
||||
controller.editableFields.contains(item.field.toString().lowercase())
|
||||
profileActivity.viewThemeUtils.material.colorTextInputLayout(holder.binding.userInfoInputLayout)
|
||||
if (profileActivity.edit &&
|
||||
profileActivity.editableFields.contains(item.field.toString().lowercase())
|
||||
) {
|
||||
holder.binding.userInfoEditTextEdit.isEnabled = true
|
||||
holder.binding.userInfoEditTextEdit.isFocusableInTouchMode = true
|
||||
@ -688,10 +688,7 @@ class ProfileActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun initUserInfoEditText(
|
||||
holder: ViewHolder,
|
||||
item: UserInfoDetailsItem
|
||||
) {
|
||||
private fun initUserInfoEditText(holder: ViewHolder, item: UserInfoDetailsItem) {
|
||||
holder.binding.userInfoEditTextEdit.setText(item.text)
|
||||
holder.binding.userInfoInputLayout.hint = item.hint
|
||||
holder.binding.userInfoEditTextEdit.addTextChangedListener(object : TextWatcher {
|
||||
@ -700,7 +697,7 @@ class ProfileActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
if (controller.edit) {
|
||||
if (profileActivity.edit) {
|
||||
displayList!![holder.adapterPosition].text = holder.binding.userInfoEditTextEdit.text.toString()
|
||||
} else {
|
||||
filteredDisplayList[holder.adapterPosition].text =
|
||||
@ -714,10 +711,7 @@ class ProfileActivity : BaseActivity() {
|
||||
})
|
||||
}
|
||||
|
||||
private fun initScopeElements(
|
||||
item: UserInfoDetailsItem,
|
||||
holder: ViewHolder
|
||||
) {
|
||||
private fun initScopeElements(item: UserInfoDetailsItem, holder: ViewHolder) {
|
||||
if (item.scope == null) {
|
||||
holder.binding.scope.visibility = View.GONE
|
||||
} else {
|
||||
@ -739,7 +733,7 @@ class ProfileActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return if (controller.edit) {
|
||||
return if (profileActivity.edit) {
|
||||
displayList!!.size
|
||||
} else {
|
||||
filteredDisplayList.size
|
||||
@ -762,7 +756,7 @@ class ProfileActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG: String = "ProfileController"
|
||||
private val TAG = ProfileActivity::class.java.simpleName
|
||||
private const val DEFAULT_CACHE_SIZE: Int = 20
|
||||
private const val DEFAULT_RETRIES: Long = 3
|
||||
private const val HIGH_EMPHASIS_ALPHA: Float = 0.87f
|
||||
|
@ -24,11 +24,7 @@ import io.reactivex.Observable
|
||||
|
||||
interface RequestAssistanceRepository {
|
||||
|
||||
fun requestAssistance(
|
||||
roomToken: String
|
||||
): Observable<RequestAssistanceModel>
|
||||
fun requestAssistance(roomToken: String): Observable<RequestAssistanceModel>
|
||||
|
||||
fun withdrawRequestAssistance(
|
||||
roomToken: String
|
||||
): Observable<WithdrawRequestAssistanceModel>
|
||||
fun withdrawRequestAssistance(roomToken: String): Observable<WithdrawRequestAssistanceModel>
|
||||
}
|
||||
|
@ -57,18 +57,14 @@ class RequestAssistanceRepositoryImpl(private val ncApi: NcApi, currentUserProvi
|
||||
).map { mapToWithdrawRequestAssistanceModel(it.ocs?.meta!!) }
|
||||
}
|
||||
|
||||
private fun mapToRequestAssistanceModel(
|
||||
response: GenericMeta
|
||||
): RequestAssistanceModel {
|
||||
private fun mapToRequestAssistanceModel(response: GenericMeta): RequestAssistanceModel {
|
||||
val success = response.statusCode == HTTP_OK
|
||||
return RequestAssistanceModel(
|
||||
success
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapToWithdrawRequestAssistanceModel(
|
||||
response: GenericMeta
|
||||
): WithdrawRequestAssistanceModel {
|
||||
private fun mapToWithdrawRequestAssistanceModel(response: GenericMeta): WithdrawRequestAssistanceModel {
|
||||
val success = response.statusCode == HTTP_OK
|
||||
return WithdrawRequestAssistanceModel(
|
||||
success
|
||||
|
@ -91,8 +91,6 @@ class ShareRecordingToChatReceiver : BroadcastReceiver() {
|
||||
// However, as we are in a broadcast receiver, this needs a TaskStackBuilder
|
||||
// combined with addNextIntentWithParentStack. For further reading, see
|
||||
// https://developer.android.com/develop/ui/views/notifications/navigation#DirectEntry
|
||||
// As we are using the conductor framework it might be hard the combine this or to keep an overview.
|
||||
// For this reason there is only a Snackbar for now until we got rid of conductor.
|
||||
|
||||
Snackbar.make(
|
||||
View(context),
|
||||
|
@ -138,6 +138,8 @@ class RemoteFileBrowserActivity : AppCompatActivity(), SelectionInterface, Swipe
|
||||
is RemoteFileBrowserItemsViewModel.FinishState -> {
|
||||
finishWithResult(state.selectedPaths)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,10 +160,7 @@ class RemoteFileBrowserActivity : AppCompatActivity(), SelectionInterface, Swipe
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadList(
|
||||
state: RemoteFileBrowserItemsViewModel.LoadedState,
|
||||
mimeTypeSelectionFilter: String?
|
||||
) {
|
||||
private fun loadList(state: RemoteFileBrowserItemsViewModel.LoadedState, mimeTypeSelectionFilter: String?) {
|
||||
val remoteFileBrowserItems = state.items
|
||||
Log.d(TAG, "Items received: $remoteFileBrowserItems")
|
||||
|
||||
|
@ -36,8 +36,7 @@ class RemoteFileBrowserItemsRepositoryImpl @Inject constructor(
|
||||
private val user: User
|
||||
get() = userProvider.currentUser.blockingGet()
|
||||
|
||||
override fun listFolder(path: String):
|
||||
Observable<List<RemoteFileBrowserItem>> {
|
||||
override fun listFolder(path: String): Observable<List<RemoteFileBrowserItem>> {
|
||||
return Observable.fromCallable {
|
||||
val operation =
|
||||
ReadFolderListingOperation(
|
||||
|
@ -26,11 +26,7 @@ import io.reactivex.Observable
|
||||
|
||||
interface CallRecordingRepository {
|
||||
|
||||
fun startRecording(
|
||||
roomToken: String
|
||||
): Observable<StartCallRecordingModel>
|
||||
fun startRecording(roomToken: String): Observable<StartCallRecordingModel>
|
||||
|
||||
fun stopRecording(
|
||||
roomToken: String
|
||||
): Observable<StopCallRecordingModel>
|
||||
fun stopRecording(roomToken: String): Observable<StopCallRecordingModel>
|
||||
}
|
||||
|
@ -37,9 +37,7 @@ class CallRecordingRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
|
||||
|
||||
var apiVersion = 1
|
||||
|
||||
override fun startRecording(
|
||||
roomToken: String
|
||||
): Observable<StartCallRecordingModel> {
|
||||
override fun startRecording(roomToken: String): Observable<StartCallRecordingModel> {
|
||||
return ncApi.startRecording(
|
||||
credentials,
|
||||
ApiUtils.getUrlForRecording(
|
||||
@ -51,9 +49,7 @@ class CallRecordingRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
|
||||
).map { mapToStartCallRecordingModel(it.ocs?.meta!!) }
|
||||
}
|
||||
|
||||
override fun stopRecording(
|
||||
roomToken: String
|
||||
): Observable<StopCallRecordingModel> {
|
||||
override fun stopRecording(roomToken: String): Observable<StopCallRecordingModel> {
|
||||
return ncApi.stopRecording(
|
||||
credentials,
|
||||
ApiUtils.getUrlForRecording(
|
||||
@ -64,18 +60,14 @@ class CallRecordingRepositoryImpl(private val ncApi: NcApi, currentUserProvider:
|
||||
).map { mapToStopCallRecordingModel(it.ocs?.meta!!) }
|
||||
}
|
||||
|
||||
private fun mapToStartCallRecordingModel(
|
||||
response: GenericMeta
|
||||
): StartCallRecordingModel {
|
||||
private fun mapToStartCallRecordingModel(response: GenericMeta): StartCallRecordingModel {
|
||||
val success = response.statusCode == HTTP_OK
|
||||
return StartCallRecordingModel(
|
||||
success
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapToStopCallRecordingModel(
|
||||
response: GenericMeta
|
||||
): StopCallRecordingModel {
|
||||
private fun mapToStopCallRecordingModel(response: GenericMeta): StopCallRecordingModel {
|
||||
val success = response.statusCode == HTTP_OK
|
||||
return StopCallRecordingModel(
|
||||
success
|
||||
|
@ -27,15 +27,7 @@ import io.reactivex.Observable
|
||||
|
||||
interface ReactionsRepository {
|
||||
|
||||
fun addReaction(
|
||||
roomToken: String,
|
||||
message: ChatMessage,
|
||||
emoji: String
|
||||
): Observable<ReactionAddedModel>
|
||||
fun addReaction(roomToken: String, message: ChatMessage, emoji: String): Observable<ReactionAddedModel>
|
||||
|
||||
fun deleteReaction(
|
||||
roomToken: String,
|
||||
message: ChatMessage,
|
||||
emoji: String
|
||||
): Observable<ReactionDeletedModel>
|
||||
fun deleteReaction(roomToken: String, message: ChatMessage, emoji: String): Observable<ReactionDeletedModel>
|
||||
}
|
||||
|
@ -36,11 +36,7 @@ class ReactionsRepositoryImpl(private val ncApi: NcApi, currentUserProvider: Cur
|
||||
val currentUser: User = currentUserProvider.currentUser.blockingGet()
|
||||
val credentials: String = ApiUtils.getCredentials(currentUser.username, currentUser.token)
|
||||
|
||||
override fun addReaction(
|
||||
roomToken: String,
|
||||
message: ChatMessage,
|
||||
emoji: String
|
||||
): Observable<ReactionAddedModel> {
|
||||
override fun addReaction(roomToken: String, message: ChatMessage, emoji: String): Observable<ReactionAddedModel> {
|
||||
return ncApi.sendReaction(
|
||||
credentials,
|
||||
ApiUtils.getUrlForMessageReaction(
|
||||
|
@ -81,8 +81,11 @@ class UnifiedSearchRepositoryImpl(private val api: NcApi, private val userProvid
|
||||
private const val ATTRIBUTE_CONVERSATION = "conversation"
|
||||
private const val ATTRIBUTE_MESSAGE_ID = "messageId"
|
||||
|
||||
private fun mapToMessageResults(data: UnifiedSearchResponseData, searchTerm: String, limit: Int):
|
||||
UnifiedSearchRepository.UnifiedSearchResults<SearchMessageEntry> {
|
||||
private fun mapToMessageResults(
|
||||
data: UnifiedSearchResponseData,
|
||||
searchTerm: String,
|
||||
limit: Int
|
||||
): UnifiedSearchRepository.UnifiedSearchResults<SearchMessageEntry> {
|
||||
val entries = data.entries?.map { it -> mapToMessage(it, searchTerm) }
|
||||
val cursor = data.cursor ?: 0
|
||||
val hasMore = entries?.size == limit
|
||||
|
@ -27,6 +27,7 @@ package com.nextcloud.talk.settings
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
@ -51,6 +52,7 @@ import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.EditText
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.view.ContextThemeWrapper
|
||||
import androidx.core.content.ContextCompat
|
||||
@ -66,6 +68,7 @@ import com.nextcloud.android.common.ui.theme.utils.ColorRole
|
||||
import com.nextcloud.talk.BuildConfig
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.activities.MainActivity
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.setAppTheme
|
||||
@ -465,17 +468,47 @@ class SettingsActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private fun removeCurrentAccount() {
|
||||
val otherUserExists = userManager.scheduleUserForDeletionWithId(currentUser!!.id!!).blockingGet()
|
||||
userManager.scheduleUserForDeletionWithId(currentUser!!.id!!).blockingGet()
|
||||
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
|
||||
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
|
||||
if (otherUserExists) {
|
||||
// TODO: find better solution once Conductor is removed
|
||||
finish()
|
||||
startActivity(intent)
|
||||
} else if (!otherUserExists) {
|
||||
Log.d(TAG, "No other users found. AccountRemovalWorker will restart the app.")
|
||||
}
|
||||
|
||||
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
|
||||
.observeForever { workInfo: WorkInfo ->
|
||||
|
||||
when (workInfo.state) {
|
||||
WorkInfo.State.SUCCEEDED -> {
|
||||
val text = String.format(
|
||||
context.resources.getString(R.string.nc_deleted_user),
|
||||
currentUser!!.displayName
|
||||
)
|
||||
Toast.makeText(
|
||||
context,
|
||||
text,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
restartApp()
|
||||
}
|
||||
WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.resources.getString(R.string.nc_common_error_sorry),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
Log.e(TAG, "something went wrong when deleting user with id " + currentUser!!.userId)
|
||||
restartApp()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun restartApp() {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun getRingtoneName(context: Context, ringtoneUri: Uri?): String {
|
||||
@ -554,7 +587,9 @@ class SettingsActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
if (("No proxy" == appPreferences.proxyType) || appPreferences.proxyType == null) {
|
||||
if (((context.resources.getString(R.string.nc_no_proxy)) == appPreferences.proxyType) ||
|
||||
appPreferences.proxyType == null
|
||||
) {
|
||||
hideProxySettings()
|
||||
} else {
|
||||
showProxySettings()
|
||||
@ -954,7 +989,7 @@ class SettingsActivity : BaseActivity() {
|
||||
proxyTypeFlow.collect { newString ->
|
||||
if (newString != state) {
|
||||
state = newString
|
||||
if (("No proxy" == newString) || newString.isEmpty()) {
|
||||
if (((context.resources.getString(R.string.nc_no_proxy)) == newString) || newString.isEmpty()) {
|
||||
hideProxySettings()
|
||||
} else {
|
||||
when (newString) {
|
||||
@ -1205,7 +1240,7 @@ class SettingsActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "SettingsController"
|
||||
private val TAG = SettingsActivity::class.java.simpleName
|
||||
private const val DURATION: Long = 2500
|
||||
private const val START_DELAY: Long = 5000
|
||||
private const val DISABLED_ALPHA: Float = 0.38f
|
||||
|
@ -28,16 +28,9 @@ import io.reactivex.Observable
|
||||
|
||||
interface SharedItemsRepository {
|
||||
|
||||
fun media(
|
||||
parameters: Parameters,
|
||||
type: SharedItemType
|
||||
): Observable<SharedItems>?
|
||||
fun media(parameters: Parameters, type: SharedItemType): Observable<SharedItems>?
|
||||
|
||||
fun media(
|
||||
parameters: Parameters,
|
||||
type: SharedItemType,
|
||||
lastKnownMessageId: Int?
|
||||
): Observable<SharedItems>?
|
||||
fun media(parameters: Parameters, type: SharedItemType, lastKnownMessageId: Int?): Observable<SharedItems>?
|
||||
|
||||
fun availableTypes(parameters: Parameters): Observable<Set<SharedItemType>>
|
||||
|
||||
|
@ -48,10 +48,7 @@ import javax.inject.Inject
|
||||
class SharedItemsRepositoryImpl @Inject constructor(private val ncApi: NcApi, private val dateUtils: DateUtils) :
|
||||
SharedItemsRepository {
|
||||
|
||||
override fun media(
|
||||
parameters: SharedItemsRepository.Parameters,
|
||||
type: SharedItemType
|
||||
): Observable<SharedItems>? {
|
||||
override fun media(parameters: SharedItemsRepository.Parameters, type: SharedItemType): Observable<SharedItems>? {
|
||||
return media(parameters, type, null)
|
||||
}
|
||||
|
||||
|
@ -98,10 +98,11 @@ class SharedItemsViewModel @Inject constructor(
|
||||
})
|
||||
}
|
||||
|
||||
private fun chooseInitialType(newTypes: Set<SharedItemType>): SharedItemType = when {
|
||||
newTypes.contains(SharedItemType.MEDIA) -> SharedItemType.MEDIA
|
||||
else -> newTypes.toList().first()
|
||||
}
|
||||
private fun chooseInitialType(newTypes: Set<SharedItemType>): SharedItemType =
|
||||
when {
|
||||
newTypes.contains(SharedItemType.MEDIA) -> SharedItemType.MEDIA
|
||||
else -> newTypes.toList().first()
|
||||
}
|
||||
|
||||
fun initialLoadItems(type: SharedItemType) {
|
||||
val state = _viewState.value
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.nextcloud.talk.translate.repositories
|
||||
|
||||
import com.nextcloud.talk.translate.repositories.model.Language
|
||||
import io.reactivex.Observable
|
||||
|
||||
interface TranslateRepository {
|
||||
@ -11,4 +12,6 @@ interface TranslateRepository {
|
||||
toLanguage: String,
|
||||
fromLanguage: String?
|
||||
): Observable<String>
|
||||
|
||||
fun getLanguages(authorization: String, url: String): Observable<List<Language>>
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user