Skip to content

Commit 69efddc

Browse files
committed
Merge branch 'trunk' into andy/revert-foreground-service-types
2 parents 491e138 + 756cbd8 commit 69efddc

31 files changed

Lines changed: 3907 additions & 3001 deletions

.idea/checkstyle-idea.xml

Lines changed: 15 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package org.wordpress.android.datasets
2+
3+
import kotlinx.coroutines.CoroutineDispatcher
4+
import kotlinx.coroutines.CoroutineScope
5+
import kotlinx.coroutines.Dispatchers
6+
import kotlinx.coroutines.launch
7+
import kotlinx.coroutines.withContext
8+
9+
/**
10+
* Helper class to handle asynchronous I/O tasks using coroutines
11+
* @see <a href="https://github.com/wordpress-mobile/WordPress-Android/pull/20937">Introduction</a>
12+
*/
13+
object AsyncTaskExecutor {
14+
/**
15+
* Execute a data loading task in the IO thread and handle the result on the main thread
16+
*/
17+
@JvmStatic
18+
fun <T> executeIo(scope: CoroutineScope, backgroundTask: () -> T, callback: AsyncTaskCallback<T>) {
19+
execute(scope, Dispatchers.IO, backgroundTask, callback)
20+
}
21+
22+
/**
23+
* Execute a data loading task in the default thread and handle the result on the main thread
24+
*/
25+
@JvmStatic
26+
fun <T> executeDefault(scope: CoroutineScope, backgroundTask: () -> T, callback: AsyncTaskCallback<T>) {
27+
execute(scope, Dispatchers.Default, backgroundTask, callback)
28+
}
29+
30+
private fun <T> execute(
31+
scope: CoroutineScope,
32+
dispatcher: CoroutineDispatcher,
33+
backgroundTask: () -> T,
34+
callback: AsyncTaskCallback<T>
35+
) {
36+
scope.launch(dispatcher) {
37+
// handle the background task
38+
val result = backgroundTask()
39+
40+
withContext(Dispatchers.Main) {
41+
// handle the result on the main thread
42+
callback.onTaskFinished(result)
43+
}
44+
}
45+
}
46+
47+
interface AsyncTaskCallback<T> {
48+
fun onTaskFinished(result: T)
49+
}
50+
}
51+

WordPress/src/main/java/org/wordpress/android/datasets/AsyncTaskHandler.kt

Lines changed: 0 additions & 33 deletions
This file was deleted.

WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/dashboard/CardViewModelSlice.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ class CardViewModelSlice @Inject constructor(
153153
identifier = buildConfigWrapper.getApplicationId(),
154154
marketingVersion = buildConfigWrapper.getAppVersionName(),
155155
platform = FEATURE_FLAG_PLATFORM_PARAMETER,
156+
osVersion = buildConfigWrapper.androidVersion
156157
)
157158
val result = cardsStore.fetchCards(payload)
158159
val error = result.error

WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ class EditPostActivity : LocaleAwareActivity(), EditorFragmentActivity, EditorIm
638638
val hasQuickPressBlogId = extras.containsKey(EditPostActivityConstants.EXTRA_QUICKPRESS_BLOG_ID)
639639

640640
// QuickPress might want to use a different blog than the current blog
641-
return if (!isActionSendOrNewMedia && !hasQuickPressFlag && hasQuickPressBlogId) {
641+
return if ((isActionSendOrNewMedia || hasQuickPressFlag) && hasQuickPressBlogId) {
642642
val localSiteId = intent.getIntExtra(EditPostActivityConstants.EXTRA_QUICKPRESS_BLOG_ID, -1)
643643
siteStore.getSiteByLocalId(localSiteId)
644644
} else {

WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderFragment.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,9 @@ class ReaderFragment : Fragment(R.layout.reader_fragment_layout), ScrollableView
396396
}
397397

398398
fun requestBookmarkTab() {
399+
if (!::viewModel.isInitialized) {
400+
viewModel = ViewModelProvider(this@ReaderFragment, viewModelFactory)[ReaderViewModel::class.java]
401+
}
399402
viewModel.bookmarkTabRequested()
400403
}
401404

WordPress/src/main/java/org/wordpress/android/ui/reader/ReaderPostListFragment.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@
153153

154154
import javax.inject.Inject;
155155

156+
import static androidx.lifecycle.LifecycleOwnerKt.getLifecycleScope;
156157
import static org.wordpress.android.fluxc.generated.AccountActionBuilder.newUpdateSubscriptionNotificationPostAction;
157158
import static org.wordpress.android.ui.reader.ReaderActivityLauncher.OpenUrlType.INTERNAL;
158159

@@ -1923,7 +1924,8 @@ private ReaderPostAdapter getPostAdapter() {
19231924
mImageManager,
19241925
mUiHelpers,
19251926
mNetworkUtilsWrapper,
1926-
mIsTopLevel
1927+
mIsTopLevel,
1928+
getLifecycleScope(this)
19271929
);
19281930
mPostAdapter.setOnFollowListener(this);
19291931
mPostAdapter.setOnPostSelectedListener(this);

WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderPostAdapter.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,13 @@
1515
import android.widget.TextView;
1616

1717
import androidx.annotation.NonNull;
18+
import androidx.lifecycle.LifecycleCoroutineScope;
1819
import androidx.recyclerview.widget.RecyclerView;
1920

2021
import org.wordpress.android.R;
2122
import org.wordpress.android.WordPress;
2223
import org.wordpress.android.analytics.AnalyticsTracker;
23-
import org.wordpress.android.datasets.AsyncTaskHandler;
24+
import org.wordpress.android.datasets.AsyncTaskExecutor;
2425
import org.wordpress.android.datasets.ReaderPostTable;
2526
import org.wordpress.android.datasets.ReaderTagTable;
2627
import org.wordpress.android.fluxc.store.AccountStore;
@@ -82,11 +83,13 @@
8283
import kotlin.jvm.functions.Function1;
8384
import kotlin.jvm.functions.Function2;
8485
import kotlin.jvm.functions.Function3;
86+
import kotlinx.coroutines.CoroutineScope;
8587

8688
public class ReaderPostAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
8789
private final ImageManager mImageManager;
8890
private final UiHelpers mUiHelpers;
8991
private final NetworkUtilsWrapper mNetworkUtilsWrapper;
92+
private final CoroutineScope mScope;
9093
private ReaderTag mCurrentTag;
9194
private long mCurrentBlogId;
9295
private long mCurrentFeedId;
@@ -372,7 +375,8 @@ private void toggleFollowButton(
372375
return;
373376
}
374377

375-
AsyncTaskHandler.load(
378+
AsyncTaskExecutor.executeIo(
379+
mScope,
376380
() -> !ReaderTagTable.isFollowedTagName(currentTag.getTagSlug()),
377381
isAskingToFollow -> {
378382
final String slugForTracking = currentTag.getTagSlug();
@@ -688,7 +692,8 @@ public ReaderPostAdapter(
688692
ImageManager imageManager,
689693
UiHelpers uiHelpers,
690694
@NonNull final NetworkUtilsWrapper networkUtilsWrapper,
691-
boolean isMainReader
695+
boolean isMainReader,
696+
LifecycleCoroutineScope scope
692697
) {
693698
super();
694699
((WordPress) context.getApplicationContext()).component().inject(this);
@@ -699,6 +704,7 @@ public ReaderPostAdapter(
699704
mNetworkUtilsWrapper = networkUtilsWrapper;
700705
mAvatarSzSmall = context.getResources().getDimensionPixelSize(R.dimen.avatar_sz_small);
701706
mIsMainReader = isMainReader;
707+
mScope = scope;
702708

703709
int displayWidth = DisplayUtils.getWindowPixelWidth(context);
704710
int cardMargin = context.getResources().getDimensionPixelSize(R.dimen.reader_card_margin);
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package org.wordpress.android.ui.voicetocontent
2+
3+
import android.content.res.Configuration
4+
import androidx.compose.animation.AnimatedContent
5+
import androidx.compose.animation.ExperimentalAnimationApi
6+
import androidx.compose.animation.animateColorAsState
7+
import androidx.compose.animation.core.tween
8+
import androidx.compose.animation.fadeIn
9+
import androidx.compose.animation.fadeOut
10+
import androidx.compose.animation.with
11+
import androidx.compose.foundation.Image
12+
import androidx.compose.foundation.background
13+
import androidx.compose.foundation.clickable
14+
import androidx.compose.foundation.isSystemInDarkTheme
15+
import androidx.compose.foundation.layout.Box
16+
import androidx.compose.foundation.layout.size
17+
import androidx.compose.foundation.shape.CircleShape
18+
import androidx.compose.material.ContentAlpha
19+
import androidx.compose.material.MaterialTheme
20+
import androidx.compose.runtime.Composable
21+
import androidx.compose.runtime.getValue
22+
import androidx.compose.runtime.mutableStateOf
23+
import androidx.compose.runtime.remember
24+
import androidx.compose.runtime.setValue
25+
import androidx.compose.ui.Alignment
26+
import androidx.compose.ui.Modifier
27+
import androidx.compose.ui.graphics.Color
28+
import androidx.compose.ui.graphics.ColorFilter
29+
import androidx.compose.ui.graphics.painter.Painter
30+
import androidx.compose.ui.res.painterResource
31+
import androidx.compose.ui.tooling.preview.Devices
32+
import androidx.compose.ui.tooling.preview.Preview
33+
import androidx.compose.ui.unit.dp
34+
import org.wordpress.android.R
35+
import org.wordpress.android.ui.compose.theme.AppTheme
36+
37+
@OptIn(ExperimentalAnimationApi::class)
38+
@Suppress("DEPRECATION")
39+
@Composable
40+
fun MicToStopIcon(model: RecordingPanelUIModel) {
41+
val isEnabled = model.isEnabled
42+
var isMic by remember { mutableStateOf(true) }
43+
val isLight = !isSystemInDarkTheme()
44+
45+
val circleColor by animateColorAsState(
46+
targetValue = if (!isEnabled) MaterialTheme.colors.onSurface.copy(alpha = 0.3f)
47+
else if (isMic) MaterialTheme.colors.primary
48+
else if (isLight) Color.Black
49+
else Color.White, label = ""
50+
)
51+
52+
val iconColor by animateColorAsState(
53+
targetValue = if (!isEnabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled)
54+
else if (isMic) Color.White
55+
else if (isLight) Color.White
56+
else Color.Black, label = ""
57+
)
58+
59+
val micIcon: Painter = painterResource(id = R.drawable.ic_mic_none_24)
60+
val stopIcon: Painter = painterResource(id = R.drawable.v2c_stop)
61+
62+
Box(
63+
contentAlignment = Alignment.Center,
64+
modifier = Modifier
65+
.size(100.dp)
66+
.background(Color.Transparent) // Ensure transparent background
67+
.clickable(
68+
enabled = isEnabled,
69+
onClick = {
70+
if (model.hasPermission) {
71+
if (isMic) {
72+
model.onMicTap?.invoke()
73+
} else {
74+
model.onStopTap?.invoke()
75+
}
76+
// isMic = !isMic
77+
} else {
78+
model.onRequestPermission?.invoke()
79+
}
80+
isMic = !isMic
81+
}
82+
)
83+
) {
84+
Box(
85+
modifier = Modifier
86+
.size(100.dp)
87+
.background(circleColor, shape = CircleShape)
88+
)
89+
if (model.hasPermission) {
90+
AnimatedContent(
91+
targetState = isMic,
92+
transitionSpec = {
93+
fadeIn(animationSpec = tween(300)) with fadeOut(animationSpec = tween(300))
94+
}, label = ""
95+
) { targetState ->
96+
val icon: Painter = if (targetState) micIcon else stopIcon
97+
val iconSize = if (targetState) 50.dp else 35.dp
98+
Image(
99+
painter = icon,
100+
contentDescription = null,
101+
modifier = Modifier.size(iconSize),
102+
colorFilter = ColorFilter.tint(iconColor)
103+
)
104+
}
105+
} else {
106+
// Display mic icon statically if permission is not granted
107+
Image(
108+
painter = micIcon,
109+
contentDescription = null,
110+
modifier = Modifier.size(50.dp),
111+
colorFilter = ColorFilter.tint(iconColor)
112+
)
113+
}
114+
}
115+
}
116+
117+
@Preview(showBackground = true)
118+
@Preview(showBackground = true, device = Devices.PIXEL_4_XL)
119+
@Preview(showBackground = true, device = Devices.PIXEL_4_XL, uiMode = Configuration.UI_MODE_NIGHT_YES)
120+
@Composable
121+
fun ExistingLayoutPreview() {
122+
AppTheme {
123+
MicToStopIcon(
124+
RecordingPanelUIModel(
125+
isEligibleForFeature = true,
126+
onMicTap = {},
127+
onStopTap = {},
128+
hasPermission = true,
129+
onRequestPermission = {},
130+
actionLabel = R.string.voice_to_content_base_header_label, isEnabled = false
131+
)
132+
)
133+
}
134+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.wordpress.android.ui.voicetocontent
2+
3+
import kotlinx.coroutines.Dispatchers
4+
import kotlinx.coroutines.withContext
5+
import org.wordpress.android.fluxc.model.SiteModel
6+
import org.wordpress.android.fluxc.model.jetpackai.JetpackAIAssistantFeature
7+
import org.wordpress.android.fluxc.network.rest.wpcom.jetpackai.JetpackAIAssistantFeatureResponse
8+
import org.wordpress.android.fluxc.store.jetpackai.JetpackAIStore
9+
import javax.inject.Inject
10+
11+
class PrepareVoiceToContentUseCase @Inject constructor(
12+
private val jetpackAIStore: JetpackAIStore
13+
) {
14+
suspend fun execute(site: SiteModel): PrepareVoiceToContentResult =
15+
withContext(Dispatchers.IO) {
16+
when (val response = jetpackAIStore.fetchJetpackAIAssistantFeature(site)) {
17+
is JetpackAIAssistantFeatureResponse.Success -> {
18+
PrepareVoiceToContentResult.Success(model = response.model)
19+
}
20+
is JetpackAIAssistantFeatureResponse.Error -> {
21+
PrepareVoiceToContentResult.Error
22+
}
23+
}
24+
}
25+
}
26+
27+
sealed class PrepareVoiceToContentResult {
28+
data class Success(val model: JetpackAIAssistantFeature) : PrepareVoiceToContentResult()
29+
data object Error : PrepareVoiceToContentResult()
30+
}

0 commit comments

Comments
 (0)