diff --git a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt index 61a9698025c..b2e56062b58 100644 --- a/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt +++ b/app/src/main/java/org/wikipedia/bridge/JavaScriptActionHandler.kt @@ -71,7 +71,7 @@ object JavaScriptActionHandler { } fun scrollToAnchor(anchorLink: String): String { - val anchor = if (anchorLink.contains("#")) anchorLink.substring(anchorLink.indexOf("#") + 1) else anchorLink + val anchor = anchorLink.substringAfter('#') return "var el = document.getElementById('$anchor');" + "window.scrollTo(0, el.offsetTop - (screen.height / 2));" + "setTimeout(function(){ el.style.backgroundColor='#fc3';" + diff --git a/app/src/main/java/org/wikipedia/categories/CategoryActivity.kt b/app/src/main/java/org/wikipedia/categories/CategoryActivity.kt index 89b3da15b12..e4043e88a1a 100644 --- a/app/src/main/java/org/wikipedia/categories/CategoryActivity.kt +++ b/app/src/main/java/org/wikipedia/categories/CategoryActivity.kt @@ -33,7 +33,8 @@ import org.wikipedia.page.Namespace import org.wikipedia.page.PageTitle import org.wikipedia.page.linkpreview.LinkPreviewDialog import org.wikipedia.readinglist.database.ReadingList -import org.wikipedia.util.* +import org.wikipedia.util.ResourceUtil +import org.wikipedia.util.StringUtil import org.wikipedia.views.DrawableItemDecoration import org.wikipedia.views.PageItemView import org.wikipedia.views.WikiErrorView @@ -138,7 +139,7 @@ class CategoryActivity : BaseActivity() { startActivity(newIntent(this, title)) } else { val entry = HistoryEntry(title, HistoryEntry.SOURCE_CATEGORY) - ExclusiveBottomSheetPresenter.show(supportFragmentManager, LinkPreviewDialog.newInstance(entry, null)) + ExclusiveBottomSheetPresenter.show(supportFragmentManager, LinkPreviewDialog.newInstance(entry)) } } diff --git a/app/src/main/java/org/wikipedia/dataclient/Service.kt b/app/src/main/java/org/wikipedia/dataclient/Service.kt index d6827a1fd60..a41fc636eaa 100644 --- a/app/src/main/java/org/wikipedia/dataclient/Service.kt +++ b/app/src/main/java/org/wikipedia/dataclient/Service.kt @@ -49,9 +49,8 @@ interface Service { ) suspend fun fullTextSearch( @Query("gsrsearch") searchTerm: String?, - @Query("gsroffset") gsrOffset: String?, @Query("gsrlimit") gsrLimit: Int, - @Query("continue") cont: String? + @Query("gsroffset") gsrOffset: Int? ): MwQueryResponse @GET(MW_API_PREFIX + "action=query&list=allusers&auwitheditsonly=1") @@ -65,10 +64,9 @@ interface Service { "&gsrnamespace=6&iiurlwidth=" + PREFERRED_THUMB_SIZE ) suspend fun fullTextSearchCommons( - @Query("gsrsearch") searchTerm: String?, - @Query("gsroffset") gsrOffset: String?, + @Query("gsrsearch") searchTerm: String, @Query("gsrlimit") gsrLimit: Int, - @Query("continue") cont: String? + @Query("gsroffset") gsrOffset: Int?, ): MwQueryResponse @GET( diff --git a/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryResponse.kt b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryResponse.kt index fcb9111b52a..0fa68e64689 100644 --- a/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryResponse.kt +++ b/app/src/main/java/org/wikipedia/dataclient/mwapi/MwQueryResponse.kt @@ -15,9 +15,9 @@ open class MwQueryResponse : MwResponse() { @Serializable class Continuation { - val sroffset = 0 - val gsroffset = 0 - val gpsoffset = 0 + val sroffset: Int? = null + val gsroffset: Int? = null + val gpsoffset: Int? = null @SerialName("continue") val continuation: String? = null @SerialName("uccontinue") val ucContinuation: String? = null @SerialName("rccontinue") val rcContinuation: String? = null diff --git a/app/src/main/java/org/wikipedia/dataclient/okhttp/OfflineCacheInterceptor.kt b/app/src/main/java/org/wikipedia/dataclient/okhttp/OfflineCacheInterceptor.kt index c13c64651b2..86f48b086d4 100644 --- a/app/src/main/java/org/wikipedia/dataclient/okhttp/OfflineCacheInterceptor.kt +++ b/app/src/main/java/org/wikipedia/dataclient/okhttp/OfflineCacheInterceptor.kt @@ -78,7 +78,7 @@ class OfflineCacheInterceptor : Interceptor { val name = line.substring(0, pos).trim() val value = line.substring(pos + 1).trim() builder.header(name, value) - if (name.lowercase(Locale.getDefault()) == "content-type") { + if (name.equals("content-type", true)) { contentType = value } } diff --git a/app/src/main/java/org/wikipedia/descriptions/DescriptionEditRevertHelpView.kt b/app/src/main/java/org/wikipedia/descriptions/DescriptionEditRevertHelpView.kt index 5b05ce6961b..0a39621ad20 100644 --- a/app/src/main/java/org/wikipedia/descriptions/DescriptionEditRevertHelpView.kt +++ b/app/src/main/java/org/wikipedia/descriptions/DescriptionEditRevertHelpView.kt @@ -3,13 +3,14 @@ package org.wikipedia.descriptions import android.content.Context import android.net.Uri import android.text.SpannableString -import android.text.Spanned import android.text.TextUtils import android.text.method.LinkMovementMethod import android.text.style.BulletSpan import android.util.AttributeSet import android.view.LayoutInflater import android.widget.ScrollView +import androidx.core.text.buildSpannedString +import androidx.core.text.inSpans import org.wikipedia.R import org.wikipedia.WikipediaApp import org.wikipedia.databinding.ViewDescriptionEditRevertHelpBinding @@ -31,14 +32,18 @@ class DescriptionEditRevertHelpView constructor(context: Context, attrs: Attribu .replace(":revertSubtitle".toRegex(), context.getString(R.string.description_edit_revert_subtitle)) .replace(":revertIntro".toRegex(), context.getString(R.string.description_edit_revert_intro)) .replace(":revertHistory".toRegex(), context.getString(R.string.description_edit_revert_history, getHistoryUri(qNumber)))) - val gapWidth = DimenUtil.roundedDpToPx(8f) - val revertReason1 = SpannableString(StringUtil.fromHtml(context.getString(R.string.description_edit_revert_reason1, context.getString(R.string.wikidata_description_guide_url)))) - val revertReason2 = SpannableString(StringUtil.fromHtml(context.getString(R.string.description_edit_revert_reason2))) - revertReason1.setSpan(BulletSpan(gapWidth), 0, revertReason1.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - revertReason2.setSpan(BulletSpan(gapWidth), 0, revertReason2.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + val revertReason1 = createReason(context.getString(R.string.description_edit_revert_reason1, + context.getString(R.string.wikidata_description_guide_url))) + val revertReason2 = createReason(context.getString(R.string.description_edit_revert_reason2)) binding.helpText.text = SpannableString(TextUtils.expandTemplate(helpStr, revertReason1, revertReason2)) } + private fun createReason(htmlString: String) = buildSpannedString { + inSpans(BulletSpan(DimenUtil.roundedDpToPx(8f))) { + append(StringUtil.fromHtml(htmlString)) + } + } + private fun getHistoryUri(qNumber: String): Uri { return Uri.Builder() .scheme(WikipediaApp.instance.wikiSite.scheme()) diff --git a/app/src/main/java/org/wikipedia/diff/ArticleEditDetailsFragment.kt b/app/src/main/java/org/wikipedia/diff/ArticleEditDetailsFragment.kt index b1ca56f8a06..49388c289d0 100644 --- a/app/src/main/java/org/wikipedia/diff/ArticleEditDetailsFragment.kt +++ b/app/src/main/java/org/wikipedia/diff/ArticleEditDetailsFragment.kt @@ -273,7 +273,7 @@ class ArticleEditDetailsFragment : Fragment(), WatchlistExpiryDialog.Callback, M startActivity(FilePageActivity.newIntent(requireContext(), viewModel.pageTitle)) } else { ExclusiveBottomSheetPresenter.show(childFragmentManager, LinkPreviewDialog.newInstance( - HistoryEntry(viewModel.pageTitle, HistoryEntry.SOURCE_EDIT_DIFF_DETAILS), null)) + HistoryEntry(viewModel.pageTitle, HistoryEntry.SOURCE_EDIT_DIFF_DETAILS))) } } binding.newerIdButton.setOnClickListener { diff --git a/app/src/main/java/org/wikipedia/diff/DiffUtil.kt b/app/src/main/java/org/wikipedia/diff/DiffUtil.kt index 209e351630f..c3be9d3b72d 100644 --- a/app/src/main/java/org/wikipedia/diff/DiffUtil.kt +++ b/app/src/main/java/org/wikipedia/diff/DiffUtil.kt @@ -9,6 +9,9 @@ import android.text.style.StrikethroughSpan import android.text.style.StyleSpan import android.view.ViewGroup import androidx.core.graphics.ColorUtils +import androidx.core.text.buildSpannedString +import androidx.core.text.inSpans +import androidx.core.text.set import androidx.recyclerview.widget.RecyclerView import org.wikipedia.R import org.wikipedia.dataclient.restbase.DiffResponse @@ -31,10 +34,10 @@ object DiffUtil { if (it.lineNumber > lastItem!!.lineEnd) { lastItem!!.lineEnd = it.lineNumber } - val str = SpannableStringBuilder(lastItem!!.parsedText) - str.append("\n") - str.append(item.parsedText) - lastItem!!.parsedText = str + lastItem!!.parsedText = buildSpannedString { + appendLine(lastItem!!.parsedText) + append(item.parsedText) + } } else { items.add(item) lastItem = item @@ -81,54 +84,43 @@ object DiffUtil { } private fun createSpannableDiffText(context: Context, diff: DiffResponse.DiffItem): CharSequence { - val spannableString = SpannableStringBuilder(diff.text.ifEmpty { "\n" }) - if (diff.text.isEmpty()) { - spannableString.setSpan(EmptyLineSpan(ResourceUtil.getThemedColor(context, android.R.attr.colorBackground), - ResourceUtil.getThemedColor(context, R.attr.placeholder_color)), 0, spannableString.length, 0) - return spannableString - } - when (diff.type) { - DiffResponse.DIFF_TYPE_LINE_ADDED -> { - updateDiffTextDecor(context, spannableString, true, 0, diff.text.length) - } - DiffResponse.DIFF_TYPE_LINE_REMOVED -> { - updateDiffTextDecor(context, spannableString, false, 0, diff.text.length) - } - DiffResponse.DIFF_TYPE_PARAGRAPH_MOVED_FROM -> { - updateDiffTextDecor(context, spannableString, false, 0, diff.text.length) - } - DiffResponse.DIFF_TYPE_PARAGRAPH_MOVED_TO -> { - updateDiffTextDecor(context, spannableString, true, 0, diff.text.length) - } - } - if (diff.highlightRanges.isNotEmpty()) { - for (highlightRange in diff.highlightRanges) { - val indices = StringUtil.utf8Indices(diff.text) - val highlightRangeStart = indices[highlightRange.start].coerceIn(0, diff.text.length) - val highlightRangeEnd = (indices.getOrElse(highlightRange.start + highlightRange.length) { indices.last() + 1 }).coerceIn(0, diff.text.length) + return buildSpannedString { + if (diff.text.isEmpty()) { + inSpans(EmptyLineSpan(ResourceUtil.getThemedColor(context, android.R.attr.colorBackground), + ResourceUtil.getThemedColor(context, R.attr.placeholder_color))) { + appendLine() + } + } else { + append(diff.text) + + when (diff.type) { + DiffResponse.DIFF_TYPE_LINE_ADDED, DiffResponse.DIFF_TYPE_PARAGRAPH_MOVED_TO -> { + updateDiffTextDecor(context, true, 0, diff.text.length) + } + DiffResponse.DIFF_TYPE_LINE_REMOVED, DiffResponse.DIFF_TYPE_PARAGRAPH_MOVED_FROM -> { + updateDiffTextDecor(context, false, 0, diff.text.length) + } + } + + for (highlightRange in diff.highlightRanges) { + val indices = StringUtil.utf8Indices(diff.text) + val highlightRangeStart = indices[highlightRange.start].coerceIn(0, diff.text.length) + val highlightRangeEnd = (indices.getOrElse(highlightRange.start + highlightRange.length) { indices.last() + 1 }).coerceIn(0, diff.text.length) + val isAddition = highlightRange.type == DiffResponse.HIGHLIGHT_TYPE_ADD - if (highlightRange.type == DiffResponse.HIGHLIGHT_TYPE_ADD) { - updateDiffTextDecor(context, spannableString, true, highlightRangeStart, highlightRangeEnd) - } else { - updateDiffTextDecor(context, spannableString, false, highlightRangeStart, highlightRangeEnd) + updateDiffTextDecor(context, isAddition, highlightRangeStart, highlightRangeEnd) } } } - return spannableString } - private fun updateDiffTextDecor(context: Context, spannableText: SpannableStringBuilder, isAddition: Boolean, start: Int, end: Int) { - val boldStyle = StyleSpan(Typeface.BOLD) - val foregroundAddedColor = ForegroundColorSpan(ResourceUtil.getThemedColor(context, R.attr.primary_color)) - val foregroundRemovedColor = ForegroundColorSpan(ResourceUtil.getThemedColor(context, R.attr.primary_color)) - spannableText.setSpan(BackgroundColorSpan(ColorUtils.setAlphaComponent(ResourceUtil.getThemedColor(context, - if (isAddition) R.attr.success_color else R.attr.destructive_color), 48)), start, end, 0) - spannableText.setSpan(boldStyle, start, end, 0) - if (isAddition) { - spannableText.setSpan(foregroundAddedColor, start, end, 0) - } else { - spannableText.setSpan(foregroundRemovedColor, start, end, 0) - spannableText.setSpan(StrikethroughSpan(), start, end, 0) + private fun SpannableStringBuilder.updateDiffTextDecor(context: Context, isAddition: Boolean, start: Int, end: Int) { + this[start, end] = BackgroundColorSpan(ColorUtils.setAlphaComponent(ResourceUtil.getThemedColor(context, + if (isAddition) R.attr.success_color else R.attr.destructive_color), 48)) + this[start, end] = StyleSpan(Typeface.BOLD) + this[start, end] = ForegroundColorSpan(ResourceUtil.getThemedColor(context, R.attr.primary_color)) + if (!isAddition) { + this[start, end] = StrikethroughSpan() } } } diff --git a/app/src/main/java/org/wikipedia/edit/FindInEditorActionProvider.kt b/app/src/main/java/org/wikipedia/edit/FindInEditorActionProvider.kt index 7d70a50a347..e3d81417de3 100644 --- a/app/src/main/java/org/wikipedia/edit/FindInEditorActionProvider.kt +++ b/app/src/main/java/org/wikipedia/edit/FindInEditorActionProvider.kt @@ -7,9 +7,9 @@ import android.view.View import org.wikipedia.edit.richtext.SyntaxHighlighter import org.wikipedia.util.DeviceUtil import org.wikipedia.util.DimenUtil +import org.wikipedia.util.StringUtil import org.wikipedia.views.FindInPageActionProvider import org.wikipedia.views.FindInPageActionProvider.FindInPageListener -import java.util.* class FindInEditorActionProvider(private val scrollView: View, private val textView: SyntaxHighlightableEditText, @@ -59,21 +59,13 @@ class FindInEditorActionProvider(private val scrollView: View, } override fun onSearchTextChanged(text: String?) { - searchQuery = if (text.isNullOrEmpty()) null else text + searchQuery = text?.ifEmpty { null } currentResultIndex = 0 resultPositions.clear() searchQuery?.let { - val searchTextLower = it.lowercase(Locale.getDefault()) - val textLower = textView.text.toString().lowercase(Locale.getDefault()) - var position = 0 - do { - position = textLower.indexOf(searchTextLower, position) - if (position >= 0) { - resultPositions.add(position) - position += searchTextLower.length - } - } while (position >= 0) + resultPositions += it.toRegex(StringUtil.SEARCH_REGEX_OPTIONS).findAll(textView.text) + .map { it.range.first } } scrollToCurrentResult() } diff --git a/app/src/main/java/org/wikipedia/edit/SyntaxHighlightViewAdapter.kt b/app/src/main/java/org/wikipedia/edit/SyntaxHighlightViewAdapter.kt index 83d4a6e2c2f..675508a74a3 100644 --- a/app/src/main/java/org/wikipedia/edit/SyntaxHighlightViewAdapter.kt +++ b/app/src/main/java/org/wikipedia/edit/SyntaxHighlightViewAdapter.kt @@ -62,7 +62,7 @@ class SyntaxHighlightViewAdapter( } override fun onPreviewLink(title: String) { - val dialog = LinkPreviewDialog.newInstance(HistoryEntry(PageTitle(title, pageTitle.wikiSite), HistoryEntry.SOURCE_INTERNAL_LINK), null) + val dialog = LinkPreviewDialog.newInstance(HistoryEntry(PageTitle(title, pageTitle.wikiSite), HistoryEntry.SOURCE_INTERNAL_LINK)) ExclusiveBottomSheetPresenter.show(activity.supportFragmentManager, dialog) editText.post { dialog.dialog?.setOnDismissListener { diff --git a/app/src/main/java/org/wikipedia/edit/WikiTextKeyboardView.kt b/app/src/main/java/org/wikipedia/edit/WikiTextKeyboardView.kt index 5efe6019e5e..eb813ba1151 100644 --- a/app/src/main/java/org/wikipedia/edit/WikiTextKeyboardView.kt +++ b/app/src/main/java/org/wikipedia/edit/WikiTextKeyboardView.kt @@ -90,34 +90,27 @@ class WikiTextKeyboardView constructor(context: Context, attrs: AttributeSet?) : binding.wikitextButtonPreviewLink.setOnClickListener { editText?.inputConnection?.let { inputConnection -> var title: String? = null - val selection = inputConnection.getSelectedText(0) - if (!selection.isNullOrEmpty() && !selection.toString().contains("[[")) { - title = trimPunctuation(selection.toString()) + val selection = inputConnection.getSelectedText(0)?.toString() + if (!selection.isNullOrEmpty() && "[[" !in selection) { + title = selection.trim('.', ',', ';', '?', '!') } else { - val before: String - val after: String - if (selection != null && selection.length > 1) { - val selectionStr = selection.toString() - before = selectionStr.substring(0, selectionStr.length / 2) - after = selectionStr.substring(selectionStr.length / 2) + val (before, after) = if (selection != null && selection.length > 1) { + selection.substring(0, selection.length / 2) to + selection.substring(selection.length / 2) } else { val peekLength = 64 - before = inputConnection.getTextBeforeCursor(peekLength, 0).toString() - after = inputConnection.getTextAfterCursor(peekLength, 0).toString() + inputConnection.getTextBeforeCursor(peekLength, 0)?.toString() to + inputConnection.getTextAfterCursor(peekLength, 0)?.toString() } - if (before.isNotEmpty() && after.isNotEmpty()) { - var str = before + after - val i1 = lastIndexOf(before, "[[") - val i2 = after.indexOf("]]") + before.length - if (i1 >= 0 && i2 > 0 && i2 > i1) { - str = str.substring(i1 + 2, i2).trim() - if (str.isNotEmpty()) { - if (str.contains("|")) { - str = str.split("\\|".toRegex()).toTypedArray()[0] - } - title = str - } + if (!before.isNullOrEmpty() && !after.isNullOrEmpty()) { + val str = before + after + val i1 = before.lastIndexOf("[[") + if (i1 >= 0) { + title = str.substring(i1 + 2).substringBefore("]]") + .trim() + .splitToSequence('|') + .firstOrNull()?.ifEmpty { null } } } } @@ -173,7 +166,6 @@ class WikiTextKeyboardView constructor(context: Context, attrs: AttributeSet?) : } companion object { - fun toggleSyntaxAroundCurrentSelection(editText: EditText?, ic: InputConnection, prefix: String, suffix: String) { editText?.let { if (it.selectionStart == it.selectionEnd) { @@ -201,32 +193,5 @@ class WikiTextKeyboardView constructor(context: Context, attrs: AttributeSet?) : } } } - - @Suppress("SameParameterValue") - fun lastIndexOf(str: String, subStr: String): Int { - var index = -1 - var a = 0 - while (a < str.length) { - val i = str.indexOf(subStr, a) - if (i >= 0) { - index = i - a = i + 1 - } else { - break - } - } - return index - } - - fun trimPunctuation(str: String): String { - var newStr = str - while (newStr.startsWith(".") || newStr.startsWith(",") || newStr.startsWith(";") || newStr.startsWith("?") || newStr.startsWith("!")) { - newStr = newStr.substring(1) - } - while (newStr.endsWith(".") || newStr.endsWith(",") || newStr.endsWith(";") || newStr.endsWith("?") || newStr.endsWith("!")) { - newStr = newStr.substring(0, newStr.length - 1) - } - return newStr - } } } diff --git a/app/src/main/java/org/wikipedia/edit/insertmedia/InsertMediaViewModel.kt b/app/src/main/java/org/wikipedia/edit/insertmedia/InsertMediaViewModel.kt index 10995516af0..c26e59d2fb9 100644 --- a/app/src/main/java/org/wikipedia/edit/insertmedia/InsertMediaViewModel.kt +++ b/app/src/main/java/org/wikipedia/edit/insertmedia/InsertMediaViewModel.kt @@ -15,7 +15,6 @@ import org.wikipedia.Constants import org.wikipedia.dataclient.Service import org.wikipedia.dataclient.ServiceFactory import org.wikipedia.dataclient.WikiSite -import org.wikipedia.dataclient.mwapi.MwQueryResponse import org.wikipedia.extensions.parcelable import org.wikipedia.extensions.serializable import org.wikipedia.page.PageTitle @@ -43,14 +42,12 @@ class InsertMediaViewModel(bundle: Bundle) : ViewModel() { loadMagicWords() } - class InsertMediaPagingSource( - val searchQuery: String, - ) : PagingSource() { - override suspend fun load(params: LoadParams): LoadResult { + class InsertMediaPagingSource(val searchQuery: String) : PagingSource() { + override suspend fun load(params: LoadParams): LoadResult { return try { val wikiSite = WikiSite(Service.COMMONS_URL) val response = ServiceFactory.get(WikiSite(Service.COMMONS_URL)) - .fullTextSearchCommons(searchQuery, params.key?.gsroffset?.toString(), params.loadSize, params.key?.continuation) + .fullTextSearchCommons(searchQuery, params.loadSize, params.key) return response.query?.pages?.let { list -> val results = list.sortedBy { it.index }.filter { it.imageInfo() != null }.map { @@ -63,7 +60,7 @@ class InsertMediaViewModel(bundle: Bundle) : ViewModel() { } pageTitle } - LoadResult.Page(results, null, response.continuation) + LoadResult.Page(results, null, response.continuation?.gsroffset) } ?: run { LoadResult.Page(emptyList(), null, null) } @@ -72,11 +69,12 @@ class InsertMediaViewModel(bundle: Bundle) : ViewModel() { } } - override fun getRefreshKey(state: PagingState): MwQueryResponse.Continuation? { + override fun getRefreshKey(state: PagingState): Int? { return null } } + @Suppress("KotlinConstantConditions") private fun loadMagicWords() { if (magicWordsLang == wikiSite.languageCode) { return diff --git a/app/src/main/java/org/wikipedia/extensions/Enum.kt b/app/src/main/java/org/wikipedia/extensions/Enum.kt new file mode 100644 index 00000000000..c6d61c28831 --- /dev/null +++ b/app/src/main/java/org/wikipedia/extensions/Enum.kt @@ -0,0 +1,8 @@ +package org.wikipedia.extensions + +import org.wikipedia.model.EnumCode +import kotlin.enums.EnumEntries + +fun EnumEntries.getByCode(code: Int): E where E : Enum, E : EnumCode { + return getOrNull(binarySearchBy(code) { it.code() }) ?: throw IllegalArgumentException("code=$code") +} diff --git a/app/src/main/java/org/wikipedia/feed/FeedContentType.kt b/app/src/main/java/org/wikipedia/feed/FeedContentType.kt index 5296392759a..f366811868e 100644 --- a/app/src/main/java/org/wikipedia/feed/FeedContentType.kt +++ b/app/src/main/java/org/wikipedia/feed/FeedContentType.kt @@ -12,7 +12,6 @@ import org.wikipedia.feed.mainpage.MainPageClient import org.wikipedia.feed.random.RandomClient import org.wikipedia.feed.suggestededits.SuggestedEditsFeedClient import org.wikipedia.model.EnumCode -import org.wikipedia.model.EnumCodeMap import org.wikipedia.settings.Prefs import org.wikipedia.util.DeviceUtil @@ -85,15 +84,11 @@ enum class FeedContentType(private val code: Int, } companion object { - private val MAP = EnumCodeMap(FeedContentType::class.java) - - fun of(code: Int): FeedContentType { return MAP[code] } - val aggregatedLanguages: List get() { val appLangCodes = WikipediaApp.instance.languageState.appLanguageCodes val list = mutableListOf() - values().filter { it.isEnabled }.forEach { type -> + entries.filter { it.isEnabled }.forEach { type -> list.addAll(appLangCodes.filter { (type.langCodesSupported.isEmpty() || type.langCodesSupported.contains(it)) && !type.langCodesDisabled.contains(it) && !list.contains(it) @@ -107,7 +102,7 @@ enum class FeedContentType(private val code: Int, val orderList = mutableListOf() val langSupportedMap = mutableMapOf>() val langDisabledMap = mutableMapOf>() - values().forEach { + entries.forEach { enabledList.add(it.isEnabled) orderList.add(it.order) langSupportedMap[it.code] = it.langCodesSupported @@ -124,7 +119,7 @@ enum class FeedContentType(private val code: Int, val orderList = Prefs.feedCardsOrder val langSupportedMap = Prefs.feedCardsLangSupported val langDisabledMap = Prefs.feedCardsLangDisabled - values().forEachIndexed { i, type -> + entries.forEachIndexed { i, type -> type.isEnabled = enabledList.getOrElse(i) { true } type.order = orderList.getOrElse(i) { i } type.langCodesSupported.clear() diff --git a/app/src/main/java/org/wikipedia/feed/model/CardType.kt b/app/src/main/java/org/wikipedia/feed/model/CardType.kt index 5f0795e9a49..d562fb1f042 100644 --- a/app/src/main/java/org/wikipedia/feed/model/CardType.kt +++ b/app/src/main/java/org/wikipedia/feed/model/CardType.kt @@ -1,6 +1,7 @@ package org.wikipedia.feed.model import android.content.Context +import org.wikipedia.extensions.getByCode import org.wikipedia.feed.FeedContentType import org.wikipedia.feed.accessibility.AccessibilityCardView import org.wikipedia.feed.announcement.AnnouncementCardView @@ -19,7 +20,6 @@ import org.wikipedia.feed.suggestededits.SuggestedEditsCardView import org.wikipedia.feed.topread.TopReadCardView import org.wikipedia.feed.view.FeedCardView import org.wikipedia.model.EnumCode -import org.wikipedia.model.EnumCodeMap enum class CardType constructor(private val code: Int, private val contentType: FeedContentType? = null) : EnumCode { @@ -139,10 +139,8 @@ enum class CardType constructor(private val code: Int, } companion object { - private val MAP = EnumCodeMap(CardType::class.java) - fun of(code: Int): CardType { - return MAP[code] + return entries.getByCode(code) } } } diff --git a/app/src/main/java/org/wikipedia/feed/view/CardFooterView.kt b/app/src/main/java/org/wikipedia/feed/view/CardFooterView.kt index debba938abb..a474a8328b2 100644 --- a/app/src/main/java/org/wikipedia/feed/view/CardFooterView.kt +++ b/app/src/main/java/org/wikipedia/feed/view/CardFooterView.kt @@ -1,14 +1,14 @@ package org.wikipedia.feed.view import android.content.Context -import android.text.SpannableStringBuilder -import android.text.Spanned import android.text.style.ImageSpan import android.util.AttributeSet import android.view.LayoutInflater import androidx.appcompat.content.res.AppCompatResources import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.graphics.drawable.toBitmap +import androidx.core.text.buildSpannedString +import androidx.core.text.inSpans import org.wikipedia.R import org.wikipedia.WikipediaApp import org.wikipedia.databinding.ViewCardFooterBinding @@ -30,19 +30,20 @@ class CardFooterView constructor(context: Context, attrs: AttributeSet? = null) } fun setFooterActionText(actionText: String, langCode: String?) { - val actionTextWithSpace = "$actionText " - val spannableStringBuilder = SpannableStringBuilder(actionTextWithSpace) val isRTL = L10nUtil.isLangRTL(langCode ?: WikipediaApp.instance.languageState.systemLanguageCode) val iconColor = ResourceUtil.getThemedColor(context, R.attr.progressive_color) // TODO: revisit this after the ImageSpan can render drawable instead of converting it to bitmap. - val arrowLeftDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_baseline_arrow_left_alt_24px)?.apply { - setTint(iconColor) - }?.toBitmap()!! - val arrowRightDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_baseline_arrow_right_alt_24px)?.apply { - setTint(iconColor) - }?.toBitmap()!! - val arrowImageSpan = ImageSpan(context, if (isRTL) arrowLeftDrawable else arrowRightDrawable) - spannableStringBuilder.setSpan(arrowImageSpan, actionTextWithSpace.length - 1, actionTextWithSpace.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) - binding.footerActionButton.text = spannableStringBuilder + val arrowLeftDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_baseline_arrow_left_alt_24px)!! + .apply { setTint(iconColor) } + .toBitmap() + val arrowRightDrawable = AppCompatResources.getDrawable(context, R.drawable.ic_baseline_arrow_right_alt_24px)!! + .apply { setTint(iconColor) } + .toBitmap() + binding.footerActionButton.text = buildSpannedString { + append("$actionText ") + inSpans(ImageSpan(context, if (isRTL) arrowLeftDrawable else arrowRightDrawable)) { + append(" ") + } + } } } diff --git a/app/src/main/java/org/wikipedia/gallery/GalleryActivity.kt b/app/src/main/java/org/wikipedia/gallery/GalleryActivity.kt index 0f5e30077ed..9cd783ad052 100644 --- a/app/src/main/java/org/wikipedia/gallery/GalleryActivity.kt +++ b/app/src/main/java/org/wikipedia/gallery/GalleryActivity.kt @@ -401,7 +401,7 @@ class GalleryActivity : BaseActivity(), LinkPreviewDialog.LoadPageCallback, Gall private fun showLinkPreview(title: PageTitle) { ExclusiveBottomSheetPresenter.show(supportFragmentManager, - LinkPreviewDialog.newInstance(HistoryEntry(title, HistoryEntry.SOURCE_GALLERY), null)) + LinkPreviewDialog.newInstance(HistoryEntry(title, HistoryEntry.SOURCE_GALLERY))) } fun setViewPagerEnabled(enabled: Boolean) { diff --git a/app/src/main/java/org/wikipedia/gallery/ImageLicense.kt b/app/src/main/java/org/wikipedia/gallery/ImageLicense.kt index be8fe90f653..a7c4073be76 100644 --- a/app/src/main/java/org/wikipedia/gallery/ImageLicense.kt +++ b/app/src/main/java/org/wikipedia/gallery/ImageLicense.kt @@ -4,7 +4,6 @@ import androidx.annotation.DrawableRes import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import org.wikipedia.R -import java.util.Locale @Serializable class ImageLicense( @@ -16,11 +15,11 @@ class ImageLicense( constructor(metadata: ExtMetadata) : this(metadata.license(), metadata.licenseShortName(), metadata.licenseUrl()) private val isLicenseCC: Boolean - get() = (licenseName.lowercase(Locale.ENGLISH).startsWith(CREATIVE_COMMONS_PREFIX) || licenseShortName.lowercase(Locale.ENGLISH).startsWith(CREATIVE_COMMONS_PREFIX)) + get() = (licenseName.startsWith(CREATIVE_COMMONS_PREFIX, true) || licenseShortName.startsWith(CREATIVE_COMMONS_PREFIX, true)) private val isLicensePD: Boolean - get() = (licenseName.lowercase(Locale.ENGLISH).startsWith(PUBLIC_DOMAIN_PREFIX) || licenseShortName.lowercase(Locale.ENGLISH).startsWith(PUBLIC_DOMAIN_PREFIX)) + get() = (licenseName.startsWith(PUBLIC_DOMAIN_PREFIX, true) || licenseShortName.startsWith(PUBLIC_DOMAIN_PREFIX, true)) private val isLicenseCCBySa: Boolean - get() = (licenseName.lowercase(Locale.ENGLISH).replace("-", "").startsWith(CC_BY_SA) || licenseShortName.lowercase(Locale.ENGLISH).replace("-", "").startsWith(CC_BY_SA)) + get() = (licenseName.replace("-", "").startsWith(CC_BY_SA, true) || licenseShortName.replace("-", "").startsWith(CC_BY_SA, true)) @get:DrawableRes val licenseIcon: Int diff --git a/app/src/main/java/org/wikipedia/gallery/MediaDownloadReceiver.kt b/app/src/main/java/org/wikipedia/gallery/MediaDownloadReceiver.kt index 8c94db4ac4a..bfad040ad79 100644 --- a/app/src/main/java/org/wikipedia/gallery/MediaDownloadReceiver.kt +++ b/app/src/main/java/org/wikipedia/gallery/MediaDownloadReceiver.kt @@ -140,7 +140,7 @@ class MediaDownloadReceiver : BroadcastReceiver() { private const val FILE_NAMESPACE = "File:" private fun trimFileNamespace(filename: String): String { - return if (filename.startsWith(FILE_NAMESPACE)) filename.substring(FILE_NAMESPACE.length) else filename + return filename.substringAfter(FILE_NAMESPACE) } } } diff --git a/app/src/main/java/org/wikipedia/history/db/HistoryEntryWithImageDao.kt b/app/src/main/java/org/wikipedia/history/db/HistoryEntryWithImageDao.kt index 81bf5d97d36..d29620cd79e 100644 --- a/app/src/main/java/org/wikipedia/history/db/HistoryEntryWithImageDao.kt +++ b/app/src/main/java/org/wikipedia/history/db/HistoryEntryWithImageDao.kt @@ -9,7 +9,8 @@ import org.wikipedia.search.SearchResult import org.wikipedia.search.SearchResults import org.wikipedia.util.StringUtil import java.text.DateFormat -import java.util.* +import java.util.Date +import java.util.Locale @Dao interface HistoryEntryWithImageDao { @@ -26,7 +27,7 @@ interface HistoryEntryWithImageDao { fun findEntriesBy(excludeSource1: Int, excludeSource2: Int, excludeSource3: Int, minTimeSpent: Int, limit: Int): List fun findHistoryItem(searchQuery: String): SearchResults { - var normalizedQuery = StringUtils.stripAccents(searchQuery).lowercase(Locale.getDefault()) + var normalizedQuery = StringUtils.stripAccents(searchQuery) if (normalizedQuery.isEmpty()) { return SearchResults() } @@ -34,7 +35,7 @@ interface HistoryEntryWithImageDao { .replace("%", "\\%").replace("_", "\\_") val entries = findEntriesBySearchTerm("%$normalizedQuery%") - .filter { StringUtil.fromHtml(it.displayTitle).toString().lowercase().contains(normalizedQuery) } + .filter { StringUtil.fromHtml(it.displayTitle).contains(normalizedQuery, true) } return if (entries.isEmpty()) SearchResults() else SearchResults(entries.take(3).map { SearchResult(toHistoryEntry(it).title, SearchResult.SearchResultType.HISTORY) }.toMutableList()) diff --git a/app/src/main/java/org/wikipedia/language/LangLinksActivity.kt b/app/src/main/java/org/wikipedia/language/LangLinksActivity.kt index aed171338c1..fe78a2b4450 100644 --- a/app/src/main/java/org/wikipedia/language/LangLinksActivity.kt +++ b/app/src/main/java/org/wikipedia/language/LangLinksActivity.kt @@ -20,7 +20,6 @@ import org.wikipedia.util.DeviceUtil import org.wikipedia.util.Resource import org.wikipedia.util.StringUtil import org.wikipedia.views.ViewAnimations -import java.util.* class LangLinksActivity : BaseActivity() { private lateinit var binding: ActivityLanglinksBinding @@ -203,13 +202,11 @@ class LangLinksActivity : BaseActivity() { fun setFilterText(filterText: String) { isSearching = true languageEntries.clear() - val filter = filterText.lowercase(Locale.getDefault()) for (entry in originalLanguageEntries) { val languageCode = entry.wikiSite.languageCode val canonicalName = app.languageState.getAppLanguageCanonicalName(languageCode).orEmpty() val localizedName = app.languageState.getAppLanguageLocalizedName(languageCode).orEmpty() - if (canonicalName.lowercase(Locale.getDefault()).contains(filter) || - localizedName.lowercase(Locale.getDefault()).contains(filter)) { + if (canonicalName.contains(filterText, true) || localizedName.contains(filterText, true)) { languageEntries.add(entry) } } diff --git a/app/src/main/java/org/wikipedia/language/LanguagesListViewModel.kt b/app/src/main/java/org/wikipedia/language/LanguagesListViewModel.kt index 913fa1ce676..1e508732392 100644 --- a/app/src/main/java/org/wikipedia/language/LanguagesListViewModel.kt +++ b/app/src/main/java/org/wikipedia/language/LanguagesListViewModel.kt @@ -13,7 +13,6 @@ import org.wikipedia.dataclient.ServiceFactory import org.wikipedia.dataclient.mwapi.SiteMatrix import org.wikipedia.util.Resource import org.wikipedia.util.log.L -import java.util.* class LanguagesListViewModel : ViewModel() { @@ -42,7 +41,7 @@ class LanguagesListViewModel : ViewModel() { fun getListBySearchTerm(context: Context, searchTerm: String?): List { val results = mutableListOf() - val filter = StringUtils.stripAccents(searchTerm.orEmpty()).lowercase(Locale.getDefault()) + val filter = StringUtils.stripAccents(searchTerm.orEmpty()) addFilteredLanguageListItems(filter, suggestedLanguageCodes, context.getString(R.string.languages_list_suggested_text), results) @@ -59,9 +58,9 @@ class LanguagesListViewModel : ViewModel() { for (code in codes) { val localizedName = StringUtils.stripAccents(WikipediaApp.instance.languageState.getAppLanguageLocalizedName(code).orEmpty()) val canonicalName = StringUtils.stripAccents(getCanonicalName(code).orEmpty()) - if (filter.isEmpty() || code.contains(filter) || - localizedName.lowercase(Locale.getDefault()).contains(filter) || - canonicalName.lowercase(Locale.getDefault()).contains(filter)) { + if (filter.isEmpty() || code.contains(filter, true) || + localizedName.contains(filter, true) || + canonicalName.contains(filter, true)) { if (first) { results.add(LanguageListItem(headerText, true)) first = false diff --git a/app/src/main/java/org/wikipedia/model/EnumCodeMap.kt b/app/src/main/java/org/wikipedia/model/EnumCodeMap.kt deleted file mode 100644 index 22ac0591703..00000000000 --- a/app/src/main/java/org/wikipedia/model/EnumCodeMap.kt +++ /dev/null @@ -1,32 +0,0 @@ -package org.wikipedia.model - -import android.util.SparseArray -import androidx.core.util.valueIterator - -class EnumCodeMap(enumeration: Class) where T : Enum, T : EnumCode { - private val map: SparseArray - - init { - map = codeToEnumMap(enumeration) - } - - operator fun get(code: Int): T { - return map.get(code) ?: throw IllegalArgumentException("code=$code") - } - - private fun codeToEnumMap(enumeration: Class): SparseArray { - val ret = SparseArray() - for (value in enumeration.enumConstants!!) { - ret.put(value.code(), value) - } - return ret - } - - fun size(): Int { - return map.size() - } - - fun valueIterator(): Iterator { - return map.valueIterator() - } -} diff --git a/app/src/main/java/org/wikipedia/model/EnumStr.kt b/app/src/main/java/org/wikipedia/model/EnumStr.kt deleted file mode 100644 index 8e201a9852e..00000000000 --- a/app/src/main/java/org/wikipedia/model/EnumStr.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.wikipedia.model - -interface EnumStr { - fun str(): String -} diff --git a/app/src/main/java/org/wikipedia/navtab/NavTab.kt b/app/src/main/java/org/wikipedia/navtab/NavTab.kt index e499c83669f..18580889d49 100644 --- a/app/src/main/java/org/wikipedia/navtab/NavTab.kt +++ b/app/src/main/java/org/wikipedia/navtab/NavTab.kt @@ -7,7 +7,6 @@ import org.wikipedia.R import org.wikipedia.feed.FeedFragment import org.wikipedia.history.HistoryFragment import org.wikipedia.model.EnumCode -import org.wikipedia.model.EnumCodeMap import org.wikipedia.readinglist.ReadingListsFragment import org.wikipedia.suggestededits.SuggestedEditsTasksFragment @@ -47,15 +46,8 @@ enum class NavTab constructor( } companion object { - - private val MAP = EnumCodeMap(NavTab::class.java) - fun of(code: Int): NavTab { - return MAP[code] - } - - fun size(): Int { - return MAP.size() + return entries[code] } } } diff --git a/app/src/main/java/org/wikipedia/navtab/NavTabFragmentPagerAdapter.kt b/app/src/main/java/org/wikipedia/navtab/NavTabFragmentPagerAdapter.kt index 73de8f531b1..af540ef0453 100644 --- a/app/src/main/java/org/wikipedia/navtab/NavTabFragmentPagerAdapter.kt +++ b/app/src/main/java/org/wikipedia/navtab/NavTabFragmentPagerAdapter.kt @@ -9,6 +9,6 @@ class NavTabFragmentPagerAdapter(fragment: Fragment) : PositionAwareFragmentStat } override fun getItemCount(): Int { - return NavTab.size() + return NavTab.entries.size } } diff --git a/app/src/main/java/org/wikipedia/navtab/NavTabLayout.kt b/app/src/main/java/org/wikipedia/navtab/NavTabLayout.kt index f51d1f11b1c..eb90565926c 100644 --- a/app/src/main/java/org/wikipedia/navtab/NavTabLayout.kt +++ b/app/src/main/java/org/wikipedia/navtab/NavTabLayout.kt @@ -8,9 +8,8 @@ import com.google.android.material.bottomnavigation.BottomNavigationView class NavTabLayout constructor(context: Context, attrs: AttributeSet) : BottomNavigationView(context, attrs) { init { menu.clear() - for (i in 0 until NavTab.size()) { - val navTab = NavTab.of(i) - menu.add(Menu.NONE, navTab.id, i, navTab.text).setIcon(navTab.icon) + NavTab.entries.forEachIndexed { index, tab -> + menu.add(Menu.NONE, tab.id, index, tab.text).setIcon(tab.icon) } } } diff --git a/app/src/main/java/org/wikipedia/notifications/NotificationCategory.kt b/app/src/main/java/org/wikipedia/notifications/NotificationCategory.kt index 9f840fa96e2..19df7f755cd 100644 --- a/app/src/main/java/org/wikipedia/notifications/NotificationCategory.kt +++ b/app/src/main/java/org/wikipedia/notifications/NotificationCategory.kt @@ -10,7 +10,6 @@ import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import org.wikipedia.R import org.wikipedia.model.EnumCode -import org.wikipedia.model.EnumCodeMap import org.wikipedia.util.log.L private const val GROUP_WIKIPEDIA_NOTIFICATIONS: String = "WIKIPEDIA_NOTIFICATIONS" @@ -45,18 +44,11 @@ enum class NotificationCategory constructor(val id: String, } companion object { - private val MENTIONS_GROUP = listOf(MENTION, EDIT_USER_TALK, EMAIL_USER, USER_RIGHTS, REVERTED) val FILTERS_GROUP = listOf(EDIT_USER_TALK, MENTION, EMAIL_USER, REVERTED, USER_RIGHTS, EDIT_THANK, MILESTONE_EDIT, LOGIN_FAIL, SYSTEM, ARTICLE_LINKED) - private val MAP = EnumCodeMap(NotificationCategory::class.java) - - private fun findOrNull(id: String): NotificationCategory? { - return MAP.valueIterator().asSequence().firstOrNull { id == it.id || id.startsWith(it.id) } - } - fun find(id: String): NotificationCategory { - return findOrNull(id) ?: MAP[0] + return entries.find { id == it.id || id.startsWith(it.id) } ?: entries[0] } fun isMentionsGroup(category: String): Boolean { @@ -97,7 +89,7 @@ enum class NotificationCategory constructor(val id: String, notificationManagerCompat.deleteNotificationChannel("READING_LIST_SYNCING_CHANNEL") notificationManagerCompat.deleteNotificationChannel("SYNCING_CHANNEL") - val notificationChannels = MAP.valueIterator().asSequence().toList().mapNotNull { + val notificationChannels = entries.mapNotNull { val notificationChannelCompat = notificationManagerCompat.getNotificationChannelCompat(it.id) if (notificationChannelCompat == null) { NotificationChannelCompat.Builder(it.id, it.importance) diff --git a/app/src/main/java/org/wikipedia/onboarding/InitialOnboardingFragment.kt b/app/src/main/java/org/wikipedia/onboarding/InitialOnboardingFragment.kt index 5ff7c5eeb7a..c88b8cce972 100644 --- a/app/src/main/java/org/wikipedia/onboarding/InitialOnboardingFragment.kt +++ b/app/src/main/java/org/wikipedia/onboarding/InitialOnboardingFragment.kt @@ -15,7 +15,6 @@ import org.wikipedia.R import org.wikipedia.activity.FragmentUtil import org.wikipedia.login.LoginActivity import org.wikipedia.model.EnumCode -import org.wikipedia.model.EnumCodeMap import org.wikipedia.settings.Prefs import org.wikipedia.settings.languages.WikipediaLanguagesActivity import org.wikipedia.util.FeedbackUtil @@ -70,7 +69,7 @@ class InitialOnboardingFragment : OnboardingFragment(), OnboardingPageView.Callb } override fun getItemCount(): Int { - return OnboardingPage.size() + return OnboardingPage.entries.size } } @@ -106,13 +105,8 @@ class InitialOnboardingFragment : OnboardingFragment(), OnboardingPageView.Callb } companion object { - private val MAP = EnumCodeMap(OnboardingPage::class.java) fun of(code: Int): OnboardingPage { - return MAP[code] - } - - fun size(): Int { - return MAP.size() + return entries[code] } } } diff --git a/app/src/main/java/org/wikipedia/page/ExtendedBottomSheetDialogFragment.kt b/app/src/main/java/org/wikipedia/page/ExtendedBottomSheetDialogFragment.kt index 07e7d3de3e7..cc762bc21e4 100644 --- a/app/src/main/java/org/wikipedia/page/ExtendedBottomSheetDialogFragment.kt +++ b/app/src/main/java/org/wikipedia/page/ExtendedBottomSheetDialogFragment.kt @@ -5,9 +5,11 @@ import android.content.Context import android.os.Bundle import android.view.MotionEvent import androidx.annotation.StyleRes +import androidx.core.view.WindowInsetsControllerCompat import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import org.wikipedia.R +import org.wikipedia.WikipediaApp import org.wikipedia.analytics.BreadcrumbsContextHelper import org.wikipedia.analytics.eventplatform.BreadCrumbLogEvent import org.wikipedia.util.DeviceUtil @@ -15,7 +17,12 @@ import org.wikipedia.util.ResourceUtil open class ExtendedBottomSheetDialogFragment : BottomSheetDialogFragment() { protected fun disableBackgroundDim() { - requireDialog().window?.setDimAmount(0f) + requireDialog().window?.let { + WindowInsetsControllerCompat(it, it.decorView).run { + isAppearanceLightStatusBars = !WikipediaApp.instance.currentTheme.isDark + } + it.setDimAmount(0f) + } } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/app/src/main/java/org/wikipedia/page/Namespace.kt b/app/src/main/java/org/wikipedia/page/Namespace.kt index 0836dcacb5e..9af6496e46e 100644 --- a/app/src/main/java/org/wikipedia/page/Namespace.kt +++ b/app/src/main/java/org/wikipedia/page/Namespace.kt @@ -1,9 +1,9 @@ package org.wikipedia.page import org.wikipedia.dataclient.WikiSite +import org.wikipedia.extensions.getByCode import org.wikipedia.language.AppLanguageLookUpTable import org.wikipedia.model.EnumCode -import org.wikipedia.model.EnumCodeMap import org.wikipedia.staticdata.* import java.util.* @@ -168,7 +168,6 @@ enum class Namespace(private val code: Int) : EnumCode { companion object { private const val TALK_MASK = 0x1 - private val MAP = EnumCodeMap(Namespace::class.java) @JvmStatic fun fromLegacyString(wiki: WikiSite, name: String?): Namespace { @@ -196,7 +195,7 @@ enum class Namespace(private val code: Int) : EnumCode { @JvmStatic fun of(code: Int): Namespace { - return MAP[code] + return entries.getByCode(code) } } } diff --git a/app/src/main/java/org/wikipedia/page/PageActivity.kt b/app/src/main/java/org/wikipedia/page/PageActivity.kt index 3eb0ebc3ea2..7d56127501d 100644 --- a/app/src/main/java/org/wikipedia/page/PageActivity.kt +++ b/app/src/main/java/org/wikipedia/page/PageActivity.kt @@ -380,7 +380,7 @@ class PageActivity : BaseActivity(), PageFragment.Callback, LinkPreviewDialog.Lo } override fun onPageShowLinkPreview(entry: HistoryEntry) { - ExclusiveBottomSheetPresenter.show(supportFragmentManager, LinkPreviewDialog.newInstance(entry, null)) + ExclusiveBottomSheetPresenter.show(supportFragmentManager, LinkPreviewDialog.newInstance(entry)) } override fun onPageLoadMainPageInForegroundTab() { diff --git a/app/src/main/java/org/wikipedia/page/action/PageActionItem.kt b/app/src/main/java/org/wikipedia/page/action/PageActionItem.kt index d7c27868ab6..5def032eb80 100644 --- a/app/src/main/java/org/wikipedia/page/action/PageActionItem.kt +++ b/app/src/main/java/org/wikipedia/page/action/PageActionItem.kt @@ -4,7 +4,6 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import org.wikipedia.R import org.wikipedia.model.EnumCode -import org.wikipedia.model.EnumCodeMap @Suppress("unused") enum class PageActionItem constructor(val id: Int, @@ -105,18 +104,8 @@ enum class PageActionItem constructor(val id: Int, } companion object { - val MAP = EnumCodeMap(PageActionItem::class.java) - - fun size(): Int { - return MAP.size() - } - - private fun findOrNull(id: Int): PageActionItem? { - return MAP.valueIterator().asSequence().firstOrNull { id == it.id || id == it.viewId } - } - fun find(id: Int): PageActionItem { - return findOrNull(id = id) ?: MAP[0] + return entries.find { id == it.id || id == it.viewId } ?: entries[0] } @DrawableRes diff --git a/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.kt b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.kt index ab19f00af95..05f3d3b8478 100644 --- a/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.kt +++ b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewDialog.kt @@ -8,9 +8,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.PopupMenu import androidx.core.app.ActivityOptionsCompat import androidx.core.os.bundleOf +import androidx.core.view.isVisible import androidx.fragment.app.viewModels import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope @@ -18,6 +20,7 @@ import androidx.lifecycle.repeatOnLifecycle import kotlinx.coroutines.launch import org.wikipedia.Constants import org.wikipedia.R +import org.wikipedia.activity.BaseActivity import org.wikipedia.activity.FragmentUtil.getCallback import org.wikipedia.analytics.eventplatform.ArticleLinkPreviewInteractionEvent import org.wikipedia.analytics.metricsplatform.ArticleLinkPreviewInteraction @@ -27,11 +30,14 @@ import org.wikipedia.dataclient.page.PageSummary import org.wikipedia.gallery.GalleryActivity import org.wikipedia.gallery.GalleryThumbnailScrollView.GalleryViewListener import org.wikipedia.history.HistoryEntry +import org.wikipedia.page.ExclusiveBottomSheetPresenter import org.wikipedia.page.ExtendedBottomSheetDialogFragment import org.wikipedia.page.Namespace import org.wikipedia.page.PageActivity import org.wikipedia.page.PageTitle +import org.wikipedia.readinglist.LongPressMenu import org.wikipedia.readinglist.ReadingListBehaviorsUtil +import org.wikipedia.readinglist.database.ReadingListPage import org.wikipedia.util.ClipboardUtil import org.wikipedia.util.FeedbackUtil import org.wikipedia.util.GeoUtil @@ -41,6 +47,9 @@ import org.wikipedia.util.ShareUtil import org.wikipedia.util.StringUtil import org.wikipedia.util.log.L import org.wikipedia.views.ViewUtil +import org.wikipedia.watchlist.WatchlistExpiry +import org.wikipedia.watchlist.WatchlistExpiryDialog +import java.util.Locale class LinkPreviewDialog : ExtendedBottomSheetDialogFragment(), LinkPreviewErrorView.Callback, DialogInterface.OnDismissListener { interface LoadPageCallback { @@ -69,12 +78,35 @@ class LinkPreviewDialog : ExtendedBottomSheetDialogFragment(), LinkPreviewErrorV ShareUtil.shareText(requireContext(), viewModel.pageTitle) true } + R.id.menu_link_preview_watch -> { + viewModel.watchOrUnwatch(viewModel.isWatched) + true + } + R.id.menu_link_preview_open_in_new_tab -> { + goToLinkedPage(true) + true + } R.id.menu_link_preview_copy_link -> { ClipboardUtil.setPlainText(requireActivity(), text = viewModel.pageTitle.uri) FeedbackUtil.showMessage(requireActivity(), R.string.address_copied) dismiss() true } + R.id.menu_link_preview_view_on_map -> { + viewModel.location?.let { + // TODO: implement this in Places branch + // startActivity(PlacesActivity.newIntent(requireContext(), viewModel.pageTitle, it)) + GeoUtil.sendGeoIntent(requireActivity(), it, StringUtil.fromHtml(viewModel.pageTitle.displayText).toString()) + } + dismiss() + true + } + R.id.menu_link_preview_get_directions -> { + viewModel.location?.let { + GeoUtil.sendGeoIntent(requireActivity(), it, StringUtil.fromHtml(viewModel.pageTitle.displayText).toString()) + } + true + } else -> false } } @@ -101,11 +133,7 @@ class LinkPreviewDialog : ExtendedBottomSheetDialogFragment(), LinkPreviewErrorV _binding = DialogLinkPreviewBinding.inflate(inflater, container, false) binding.linkPreviewToolbar.setOnClickListener { goToLinkedPage(false) } binding.linkPreviewOverflowButton.setOnClickListener { - PopupMenu(requireActivity(), binding.linkPreviewOverflowButton).run { - inflate(R.menu.menu_link_preview) - setOnMenuItemClickListener(menuListener) - show() - } + setupOverflowMenu() } L10nUtil.setConditionalLayoutDirection(binding.root, viewModel.pageTitle.wikiSite.languageCode) @@ -125,6 +153,10 @@ class LinkPreviewDialog : ExtendedBottomSheetDialogFragment(), LinkPreviewErrorV is LinkPreviewViewState.Gallery -> { renderGalleryState(it) } + is LinkPreviewViewState.Watch -> { + showWatchlistSnackbar(requireActivity() as BaseActivity, viewModel.pageTitle) + dismiss() + } is LinkPreviewViewState.Completed -> { binding.linkPreviewProgress.visibility = View.GONE } @@ -135,6 +167,20 @@ class LinkPreviewDialog : ExtendedBottomSheetDialogFragment(), LinkPreviewErrorV return binding.root } + private fun setupOverflowMenu() { + val popupMenu = PopupMenu(requireActivity(), binding.linkPreviewOverflowButton) + popupMenu.inflate(R.menu.menu_link_preview) + popupMenu.menu.findItem(R.id.menu_link_preview_add_to_list).isVisible = !viewModel.fromPlaces + popupMenu.menu.findItem(R.id.menu_link_preview_share_page).isVisible = !viewModel.fromPlaces + popupMenu.menu.findItem(R.id.menu_link_preview_watch).isVisible = viewModel.fromPlaces + popupMenu.menu.findItem(R.id.menu_link_preview_watch).title = getString(if (viewModel.isWatched) R.string.menu_page_unwatch else R.string.menu_page_watch) + popupMenu.menu.findItem(R.id.menu_link_preview_open_in_new_tab).isVisible = viewModel.fromPlaces + popupMenu.menu.findItem(R.id.menu_link_preview_view_on_map).isVisible = !viewModel.fromPlaces && viewModel.location != null + popupMenu.menu.findItem(R.id.menu_link_preview_get_directions).isVisible = viewModel.fromPlaces + popupMenu.setOnMenuItemClickListener(menuListener) + popupMenu.show() + } + private fun renderGalleryState(it: LinkPreviewViewState.Gallery) { binding.linkPreviewThumbnailGallery.setGalleryList(it.data) binding.linkPreviewThumbnailGallery.listener = galleryViewListener @@ -159,6 +205,14 @@ class LinkPreviewDialog : ExtendedBottomSheetDialogFragment(), LinkPreviewErrorV revision = summary.revision binding.linkPreviewTitle.text = StringUtil.fromHtml(summary.displayTitle) + if (viewModel.fromPlaces) { + viewModel.location?.let { startLocation -> + viewModel.lastKnownLocation?.let { endLocation -> + binding.linkPreviewDistance.isVisible = true + binding.linkPreviewDistance.text = GeoUtil.getDistanceWithUnit(startLocation, endLocation, Locale.getDefault()) + } + } + } showPreview(LinkPreviewContents(summary, viewModel.pageTitle.wikiSite)) } @@ -168,26 +222,42 @@ class LinkPreviewDialog : ExtendedBottomSheetDialogFragment(), LinkPreviewErrorV showError(throwable) } + override fun onStart() { + super.onStart() + if (viewModel.fromPlaces) { + disableBackgroundDim() + } + } + override fun onResume() { super.onResume() val containerView = requireDialog().findViewById(R.id.container) if (overlayView == null && containerView != null) { LinkPreviewOverlayView(requireContext()).let { overlayView = it - it.callback = OverlayViewCallback() - it.setPrimaryButtonText( - L10nUtil.getStringForArticleLanguage( - viewModel.pageTitle, - if (viewModel.pageTitle.namespace() === Namespace.TALK || viewModel.pageTitle.namespace() === Namespace.USER_TALK) R.string.button_continue_to_talk_page else R.string.button_continue_to_article - ) - ) - it.setSecondaryButtonText( - L10nUtil.getStringForArticleLanguage( - viewModel.pageTitle, - R.string.menu_long_press_open_in_new_tab + if (viewModel.fromPlaces) { + it.callback = OverlayViewPlacesCallback() + it.setPrimaryButtonText( + L10nUtil.getStringForArticleLanguage(viewModel.pageTitle, R.string.link_preview_dialog_share_button) + ) + it.setSecondaryButtonText( + L10nUtil.getStringForArticleLanguage(viewModel.pageTitle, R.string.link_preview_dialog_save_button) + ) + it.setTertiaryButtonText( + L10nUtil.getStringForArticleLanguage(viewModel.pageTitle, R.string.link_preview_dialog_read_button) + ) + } else { + it.callback = OverlayViewCallback() + it.setPrimaryButtonText( + L10nUtil.getStringForArticleLanguage(viewModel.pageTitle, + if (viewModel.pageTitle.namespace() === Namespace.TALK || viewModel.pageTitle.namespace() === Namespace.USER_TALK) R.string.button_continue_to_talk_page else R.string.button_continue_to_article ) - ) - it.showTertiaryButton(viewModel.location != null) + ) + it.setSecondaryButtonText( + L10nUtil.getStringForArticleLanguage(viewModel.pageTitle, R.string.menu_long_press_open_in_new_tab) + ) + it.showTertiaryButton(false) + } containerView.addView(it) } } @@ -221,11 +291,55 @@ class LinkPreviewDialog : ExtendedBottomSheetDialogFragment(), LinkPreviewErrorV dismiss() } + private fun showWatchlistSnackbar(activity: AppCompatActivity, pageTitle: PageTitle) { + viewModel.pageTitle.let { + if (!viewModel.isWatched) { + FeedbackUtil.showMessage(this, getString(R.string.watchlist_page_removed_from_watchlist_snackbar, it.displayText)) + } else if (viewModel.isWatched) { + val snackbar = FeedbackUtil.makeSnackbar(requireActivity(), + getString(R.string.watchlist_page_add_to_watchlist_snackbar, it.displayText, getString(WatchlistExpiry.NEVER.stringId))) + snackbar.setAction(R.string.watchlist_page_add_to_watchlist_snackbar_action) { + ExclusiveBottomSheetPresenter.show(activity.supportFragmentManager, WatchlistExpiryDialog.newInstance(pageTitle, WatchlistExpiry.NEVER)) + } + snackbar.show() + } + } + } + private fun doAddToList() { ReadingListBehaviorsUtil.addToDefaultList(requireActivity(), viewModel.pageTitle, true, Constants.InvokeSource.LINK_PREVIEW_MENU) dialog?.dismiss() } + private fun showReadingListPopupMenu(anchorView: View) { + if (viewModel.isInReadingList) { + LongPressMenu(anchorView, object : LongPressMenu.Callback { + override fun onOpenLink(entry: HistoryEntry) { } + + override fun onOpenInNewTab(entry: HistoryEntry) { } + + override fun onAddRequest(entry: HistoryEntry, addToDefault: Boolean) { + ReadingListBehaviorsUtil.addToDefaultList(requireActivity(), viewModel.pageTitle, addToDefault, Constants.InvokeSource.LINK_PREVIEW_MENU) + dismiss() + } + + override fun onMoveRequest(page: ReadingListPage?, entry: HistoryEntry) { + page?.let { readingListPage -> + ReadingListBehaviorsUtil.moveToList(requireActivity(), readingListPage.listId, viewModel.pageTitle, Constants.InvokeSource.LINK_PREVIEW_MENU) + } + dismiss() + } + + override fun onRemoveRequest() { + dismiss() + } + }).show(HistoryEntry(viewModel.pageTitle, HistoryEntry.SOURCE_INTERNAL_LINK)) + } else { + ReadingListBehaviorsUtil.addToDefaultList(requireActivity(), viewModel.pageTitle, true, Constants.InvokeSource.LINK_PREVIEW_MENU) + dismiss() + } + } + private fun showPreview(contents: LinkPreviewContents) { viewModel.loadGallery(revision) setPreviewContents(contents) @@ -276,21 +390,19 @@ class LinkPreviewDialog : ExtendedBottomSheetDialogFragment(), LinkPreviewErrorV ViewUtil.loadImage(binding.linkPreviewThumbnail, it) } overlayView?.run { - setPrimaryButtonText( + if (!viewModel.fromPlaces) { + setPrimaryButtonText( L10nUtil.getStringForArticleLanguage( - viewModel.pageTitle, - if (contents.isDisambiguation) R.string.button_continue_to_disambiguation - else if (viewModel.pageTitle.namespace() === Namespace.TALK || viewModel.pageTitle.namespace() === Namespace.USER_TALK) R.string.button_continue_to_talk_page - else R.string.button_continue_to_article + viewModel.pageTitle, + if (contents.isDisambiguation) R.string.button_continue_to_disambiguation + else if (viewModel.pageTitle.namespace() === Namespace.TALK || viewModel.pageTitle.namespace() === Namespace.USER_TALK) R.string.button_continue_to_talk_page + else R.string.button_continue_to_article ) - ) - } - } - - private fun goToExternalMapsApp() { - viewModel.location?.let { - dismiss() - GeoUtil.sendGeoIntent(requireActivity(), it, viewModel.pageTitle.displayText) + ) + } else if (viewModel.fromPlaces) { + setSecondaryButtonText(L10nUtil.getStringForArticleLanguage(viewModel.pageTitle, + if (viewModel.isInReadingList) R.string.link_preview_dialog_saved_button else R.string.link_preview_dialog_save_button)) + } } } @@ -325,17 +437,40 @@ class LinkPreviewDialog : ExtendedBottomSheetDialogFragment(), LinkPreviewErrorV } override fun onTertiaryClick() { - goToExternalMapsApp() + // ignore + } + } + + private inner class OverlayViewPlacesCallback : LinkPreviewOverlayView.Callback { + override fun onPrimaryClick() { + ShareUtil.shareText(requireContext(), viewModel.pageTitle) + } + + override fun onSecondaryClick() { + overlayView?.let { + showReadingListPopupMenu(it.secondaryButtonView) + } + } + + override fun onTertiaryClick() { + goToLinkedPage(false) } } companion object { const val ARG_ENTRY = "entry" const val ARG_LOCATION = "location" + const val ARG_LAST_KNOWN_LOCATION = "lastKnownLocation" + const val ARG_FROM_PLACES = "fromPlaces" - fun newInstance(entry: HistoryEntry, location: Location?): LinkPreviewDialog { + fun newInstance(entry: HistoryEntry, location: Location? = null, lastKnownLocation: Location? = null, fromPlaces: Boolean = false): LinkPreviewDialog { return LinkPreviewDialog().apply { - arguments = bundleOf(ARG_ENTRY to entry, ARG_LOCATION to location) + arguments = bundleOf( + ARG_ENTRY to entry, + ARG_LOCATION to location, + ARG_LAST_KNOWN_LOCATION to lastKnownLocation, + ARG_FROM_PLACES to fromPlaces + ) } } } diff --git a/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewOverlayView.kt b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewOverlayView.kt index c7ec02d04fb..50eeb8ddf66 100644 --- a/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewOverlayView.kt +++ b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewOverlayView.kt @@ -19,6 +19,7 @@ class LinkPreviewOverlayView : FrameLayout { private val binding = ViewLinkPreviewOverlayBinding.inflate(LayoutInflater.from(context), this, true) var callback: Callback? = null + val secondaryButtonView get() = binding.linkPreviewSecondaryButton init { binding.linkPreviewPrimaryButton.setOnClickListener { @@ -47,4 +48,8 @@ class LinkPreviewOverlayView : FrameLayout { fun showTertiaryButton(show: Boolean) { binding.linkPreviewTertiaryButton.visibility = if (show) VISIBLE else GONE } + + fun setTertiaryButtonText(text: CharSequence?) { + binding.linkPreviewTertiaryButton.text = text + } } diff --git a/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewViewModel.kt b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewViewModel.kt index 1dafda2daa7..58ead3af395 100644 --- a/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewViewModel.kt +++ b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewViewModel.kt @@ -6,28 +6,37 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch +import org.wikipedia.analytics.eventplatform.WatchlistAnalyticsHelper +import org.wikipedia.database.AppDatabase import org.wikipedia.dataclient.ServiceFactory import org.wikipedia.extensions.parcelable import org.wikipedia.history.HistoryEntry import org.wikipedia.page.PageTitle -import org.wikipedia.page.linkpreview.LinkPreviewDialog.Companion.ARG_LOCATION import org.wikipedia.settings.Prefs import org.wikipedia.util.log.L +import org.wikipedia.watchlist.WatchlistExpiry class LinkPreviewViewModel(bundle: Bundle) : ViewModel() { private val _uiState = MutableStateFlow(LinkPreviewViewState.Loading) val uiState = _uiState.asStateFlow() val historyEntry = bundle.parcelable(LinkPreviewDialog.ARG_ENTRY)!! var pageTitle = historyEntry.title - val location = bundle.parcelable(ARG_LOCATION) + var location = bundle.parcelable(LinkPreviewDialog.ARG_LOCATION) + val fromPlaces = bundle.getBoolean(LinkPreviewDialog.ARG_FROM_PLACES, false) + val lastKnownLocation = bundle.parcelable(LinkPreviewDialog.ARG_LAST_KNOWN_LOCATION) + var isInReadingList = false + + var isWatched = false + var hasWatchlistExpiry = false init { loadContent() } - fun loadContent() { + private fun loadContent() { viewModelScope.launch(CoroutineExceptionHandler { _, throwable -> _uiState.value = LinkPreviewViewState.Error(throwable) }) { @@ -49,6 +58,19 @@ class LinkPreviewViewModel(bundle: Bundle) : ViewModel() { } else if (!oldFragment.isNullOrEmpty()) { pageTitle.fragment = oldFragment } + + if (fromPlaces) { + val watchStatus = ServiceFactory.get(pageTitle.wikiSite).getWatchedStatus(pageTitle.prefixedText).query?.firstPage() + isWatched = watchStatus?.watched ?: false + + val readingList = AppDatabase.instance.readingListPageDao().findPageInAnyList(pageTitle) + isInReadingList = readingList != null + } + + if (location == null) { + location = summary.coordinates + } + _uiState.value = LinkPreviewViewState.Content(summary) } } @@ -82,6 +104,31 @@ class LinkPreviewViewModel(bundle: Bundle) : ViewModel() { } } + fun watchOrUnwatch(unwatch: Boolean) { + if (isWatched) { + WatchlistAnalyticsHelper.logRemovedFromWatchlist(pageTitle) + } else { + WatchlistAnalyticsHelper.logAddedToWatchlist(pageTitle) + } + viewModelScope.launch(CoroutineExceptionHandler { _, throwable -> + L.w("Failed to fetch watch status.", throwable) + }) { + val token = ServiceFactory.get(pageTitle.wikiSite).getWatchToken().query?.watchToken() + val response = ServiceFactory.get(pageTitle.wikiSite) + .watch(if (unwatch) 1 else null, null, pageTitle.prefixedText, WatchlistExpiry.NEVER.expiry, token!!) + + if (unwatch) { + WatchlistAnalyticsHelper.logRemovedFromWatchlistSuccess(pageTitle) + } else { + WatchlistAnalyticsHelper.logAddedToWatchlistSuccess(pageTitle) + } + response.getFirst()?.let { + isWatched = it.watched + _uiState.value = LinkPreviewViewState.Watch(isWatched) + } + } + } + class Factory(private val bunble: Bundle) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T { diff --git a/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewViewState.kt b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewViewState.kt index b1ff79bfbfd..6eb1e6791e0 100644 --- a/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewViewState.kt +++ b/app/src/main/java/org/wikipedia/page/linkpreview/LinkPreviewViewState.kt @@ -4,9 +4,10 @@ import org.wikipedia.dataclient.mwapi.MwQueryPage import org.wikipedia.dataclient.page.PageSummary sealed class LinkPreviewViewState { - object Loading : LinkPreviewViewState() - object Completed : LinkPreviewViewState() + data object Loading : LinkPreviewViewState() + data object Completed : LinkPreviewViewState() data class Error(val throwable: Throwable) : LinkPreviewViewState() data class Content(val data: PageSummary) : LinkPreviewViewState() data class Gallery(val data: List) : LinkPreviewViewState() + data class Watch(val isWatch: Boolean) : LinkPreviewViewState() } diff --git a/app/src/main/java/org/wikipedia/readinglist/LongPressMenu.kt b/app/src/main/java/org/wikipedia/readinglist/LongPressMenu.kt index 5bea4fe40ee..8fa8372b66c 100644 --- a/app/src/main/java/org/wikipedia/readinglist/LongPressMenu.kt +++ b/app/src/main/java/org/wikipedia/readinglist/LongPressMenu.kt @@ -28,6 +28,7 @@ class LongPressMenu(private val anchorView: View, private val existsInAnyList: B fun onOpenInNewTab(entry: HistoryEntry) fun onAddRequest(entry: HistoryEntry, addToDefault: Boolean) fun onMoveRequest(page: ReadingListPage?, entry: HistoryEntry) + fun onRemoveRequest() { /* ignore by default */ } } @MenuRes @@ -89,7 +90,7 @@ class LongPressMenu(private val anchorView: View, private val existsInAnyList: B listsContainingPage?.let { list -> RemoveFromReadingListsDialog(list).deleteOrShowDialog(getActivity()) { readingLists, _ -> entry?.let { - if (anchorView.isAttachedToWindow) { + if (!getActivity().isDestroyed) { val readingListNames = readingLists.map { readingList -> readingList.title }.run { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { ListFormatter.getInstance().format(this) @@ -139,6 +140,7 @@ class LongPressMenu(private val anchorView: View, private val existsInAnyList: B } R.id.menu_long_press_remove_from_lists -> { deleteOrShowDialog() + callback?.onRemoveRequest() true } R.id.menu_long_press_share_page -> { diff --git a/app/src/main/java/org/wikipedia/readinglist/ReadingListBehaviorsUtil.kt b/app/src/main/java/org/wikipedia/readinglist/ReadingListBehaviorsUtil.kt index 4bdc3ae7181..a18bbd2cace 100644 --- a/app/src/main/java/org/wikipedia/readinglist/ReadingListBehaviorsUtil.kt +++ b/app/src/main/java/org/wikipedia/readinglist/ReadingListBehaviorsUtil.kt @@ -29,7 +29,6 @@ import org.wikipedia.util.FeedbackUtil import org.wikipedia.util.StringUtil import org.wikipedia.util.log.L import org.wikipedia.views.CircularProgressBar.Companion.MIN_PROGRESS -import java.util.Locale object ReadingListBehaviorsUtil { @@ -385,14 +384,14 @@ object ReadingListBehaviorsUtil { return result } - val normalizedQuery = StringUtils.stripAccents(searchQuery).lowercase(Locale.getDefault()) + val normalizedQuery = StringUtils.stripAccents(searchQuery) var lastListItemIndex = 0 lists.forEach { list -> - if (StringUtils.stripAccents(list.title).lowercase(Locale.getDefault()).contains(normalizedQuery)) { + if (StringUtils.stripAccents(list.title).contains(normalizedQuery, true)) { result.add(lastListItemIndex++, list) } list.pages.forEach { page -> - if (page.accentAndCaseInvariantTitle().contains(normalizedQuery)) { + if (page.accentInvariantTitle.contains(normalizedQuery, true)) { if (result.none { it is ReadingListPage && it.lang == page.lang && it.apiTitle == page.apiTitle }) { result.add(page) } diff --git a/app/src/main/java/org/wikipedia/readinglist/ReadingListsFragment.kt b/app/src/main/java/org/wikipedia/readinglist/ReadingListsFragment.kt index acea1c769f4..b3d6d0c2829 100644 --- a/app/src/main/java/org/wikipedia/readinglist/ReadingListsFragment.kt +++ b/app/src/main/java/org/wikipedia/readinglist/ReadingListsFragment.kt @@ -5,14 +5,14 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle -import android.text.SpannableString -import android.text.style.ForegroundColorSpan import android.view.* import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.view.ActionMode import androidx.core.graphics.ColorUtils +import androidx.core.text.buildSpannedString +import androidx.core.text.color import androidx.core.view.MenuItemCompat import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -598,10 +598,11 @@ class ReadingListsFragment : Fragment(), SortReadingListsDialog.Callback, Readin val deleteItem = menu.findItem(R.id.menu_delete_selected) val exportItem = menu.findItem(R.id.menu_export_selected) val exportItemTitleColor = ResourceUtil.getThemedColor(requireContext(), R.attr.progressive_color) - val exportItemAlphaColor = ColorUtils.setAlphaComponent(exportItemTitleColor, alpha) - val spanString = SpannableString(exportItem.title.toString()) - spanString.setSpan(ForegroundColorSpan(exportItemAlphaColor), 0, spanString.length, 0) - exportItem.title = spanString + exportItem.title = buildSpannedString { + color(ColorUtils.setAlphaComponent(exportItemTitleColor, alpha)) { + append(exportItem.title) + } + } deleteItem.icon?.alpha = alpha exportItem.isEnabled = isEnabled deleteItem.isEnabled = isEnabled diff --git a/app/src/main/java/org/wikipedia/readinglist/database/ReadingList.kt b/app/src/main/java/org/wikipedia/readinglist/database/ReadingList.kt index 93a2d7b07d9..f5cd56775b8 100644 --- a/app/src/main/java/org/wikipedia/readinglist/database/ReadingList.kt +++ b/app/src/main/java/org/wikipedia/readinglist/database/ReadingList.kt @@ -7,7 +7,6 @@ import org.apache.commons.lang3.StringUtils import org.wikipedia.R import org.wikipedia.WikipediaApp import java.io.Serializable -import java.util.* // TODO: create default reading list upon initial DB creation. @@ -26,8 +25,10 @@ class ReadingList( @Ignore val pages = mutableListOf() - @Transient - private var accentAndCaseInvariantTitle: String? = null + @delegate:Transient + val accentInvariantTitle: String by lazy { + StringUtils.stripAccents(title) + } @Transient var selected = false @@ -45,13 +46,6 @@ class ReadingList( val sizeBytesFromPages get() = pages.sumOf { if (it.offline) it.sizeBytes else 0 } - fun accentAndCaseInvariantTitle(): String { - if (accentAndCaseInvariantTitle == null) { - accentAndCaseInvariantTitle = StringUtils.stripAccents(title).lowercase(Locale.getDefault()) - } - return accentAndCaseInvariantTitle!! - } - fun touch() { atime = System.currentTimeMillis() } @@ -73,8 +67,8 @@ class ReadingList( fun sort(list: ReadingList, sortMode: Int) { when (sortMode) { - SORT_BY_NAME_ASC -> list.pages.sortWith { lhs: ReadingListPage, rhs: ReadingListPage -> lhs.accentAndCaseInvariantTitle().compareTo(rhs.accentAndCaseInvariantTitle()) } - SORT_BY_NAME_DESC -> list.pages.sortWith { lhs: ReadingListPage, rhs: ReadingListPage -> rhs.accentAndCaseInvariantTitle().compareTo(lhs.accentAndCaseInvariantTitle()) } + SORT_BY_NAME_ASC -> list.pages.sortWith { lhs: ReadingListPage, rhs: ReadingListPage -> lhs.accentInvariantTitle.compareTo(rhs.accentInvariantTitle, true) } + SORT_BY_NAME_DESC -> list.pages.sortWith { lhs: ReadingListPage, rhs: ReadingListPage -> rhs.accentInvariantTitle.compareTo(lhs.accentInvariantTitle, true) } SORT_BY_RECENT_ASC -> list.pages.sortWith { lhs: ReadingListPage, rhs: ReadingListPage -> lhs.mtime.compareTo(rhs.mtime) } SORT_BY_RECENT_DESC -> list.pages.sortWith { lhs: ReadingListPage, rhs: ReadingListPage -> rhs.mtime.compareTo(lhs.mtime) } } @@ -82,8 +76,8 @@ class ReadingList( fun sort(lists: MutableList, sortMode: Int) { when (sortMode) { - SORT_BY_NAME_ASC -> lists.sortWith { lhs: ReadingList, rhs: ReadingList -> lhs.accentAndCaseInvariantTitle().compareTo(rhs.accentAndCaseInvariantTitle()) } - SORT_BY_NAME_DESC -> lists.sortWith { lhs: ReadingList, rhs: ReadingList -> rhs.accentAndCaseInvariantTitle().compareTo(lhs.accentAndCaseInvariantTitle()) } + SORT_BY_NAME_ASC -> lists.sortWith { lhs: ReadingList, rhs: ReadingList -> lhs.accentInvariantTitle.compareTo(rhs.accentInvariantTitle, true) } + SORT_BY_NAME_DESC -> lists.sortWith { lhs: ReadingList, rhs: ReadingList -> rhs.accentInvariantTitle.compareTo(lhs.accentInvariantTitle, true) } SORT_BY_RECENT_ASC -> lists.sortWith { lhs: ReadingList, rhs: ReadingList -> rhs.mtime.compareTo(lhs.mtime) } SORT_BY_RECENT_DESC -> lists.sortWith { lhs: ReadingList, rhs: ReadingList -> lhs.mtime.compareTo(rhs.mtime) } } @@ -98,14 +92,14 @@ class ReadingList( when (sortMode) { SORT_BY_NAME_ASC -> lists.sortWith { lhs: Any?, rhs: Any? -> if (lhs is ReadingList && rhs is ReadingList) { - lhs.accentAndCaseInvariantTitle().compareTo(rhs.accentAndCaseInvariantTitle()) + lhs.accentInvariantTitle.compareTo(rhs.accentInvariantTitle, true) } else { 0 } } SORT_BY_NAME_DESC -> lists.sortWith { lhs: Any?, rhs: Any? -> if (lhs is ReadingList && rhs is ReadingList) { - rhs.accentAndCaseInvariantTitle().compareTo(lhs.accentAndCaseInvariantTitle()) + rhs.accentInvariantTitle.compareTo(lhs.accentInvariantTitle, true) } else { 0 } diff --git a/app/src/main/java/org/wikipedia/readinglist/database/ReadingListPage.kt b/app/src/main/java/org/wikipedia/readinglist/database/ReadingListPage.kt index 55b85e7e054..eb90b41baa6 100644 --- a/app/src/main/java/org/wikipedia/readinglist/database/ReadingListPage.kt +++ b/app/src/main/java/org/wikipedia/readinglist/database/ReadingListPage.kt @@ -10,7 +10,6 @@ import org.wikipedia.page.PageTitle import org.wikipedia.settings.Prefs import org.wikipedia.util.StringUtil import java.io.Serializable -import java.util.* @Entity data class ReadingListPage( @@ -40,7 +39,10 @@ data class ReadingListPage( atime = now } - @Transient private var accentAndCaseInvariantTitle: String? = null + @delegate:Transient + val accentInvariantTitle: String by lazy { + StringUtils.stripAccents(StringUtil.fromHtml(displayTitle).toString()) + } @Transient var downloadProgress = 0 @@ -48,13 +50,6 @@ data class ReadingListPage( val saving get() = offline && (status == STATUS_QUEUE_FOR_SAVE || status == STATUS_QUEUE_FOR_FORCED_SAVE) - fun accentAndCaseInvariantTitle(): String { - if (accentAndCaseInvariantTitle == null) { - accentAndCaseInvariantTitle = StringUtils.stripAccents(StringUtil.fromHtml(displayTitle).toString()).lowercase(Locale.getDefault()) - } - return accentAndCaseInvariantTitle!! - } - fun touch() { atime = System.currentTimeMillis() } diff --git a/app/src/main/java/org/wikipedia/readinglist/db/ReadingListPageDao.kt b/app/src/main/java/org/wikipedia/readinglist/db/ReadingListPageDao.kt index 0feba1b9861..657bcb23c29 100644 --- a/app/src/main/java/org/wikipedia/readinglist/db/ReadingListPageDao.kt +++ b/app/src/main/java/org/wikipedia/readinglist/db/ReadingListPageDao.kt @@ -14,7 +14,6 @@ import org.wikipedia.savedpages.SavedPageSyncService import org.wikipedia.search.SearchResult import org.wikipedia.search.SearchResults import org.wikipedia.util.StringUtil -import java.util.* @Dao interface ReadingListPageDao { @@ -139,7 +138,7 @@ interface ReadingListPageDao { } fun findPageForSearchQueryInAnyList(searchQuery: String): SearchResults { - var normalizedQuery = StringUtils.stripAccents(searchQuery).lowercase(Locale.getDefault()) + var normalizedQuery = StringUtils.stripAccents(searchQuery) if (normalizedQuery.isEmpty()) { return SearchResults() } @@ -147,7 +146,7 @@ interface ReadingListPageDao { .replace("%", "\\%").replace("_", "\\_") val pages = findPageBySearchTerm("%$normalizedQuery%") - .filter { StringUtil.fromHtml(it.accentAndCaseInvariantTitle()).contains(normalizedQuery) } + .filter { StringUtil.fromHtml(it.accentInvariantTitle).contains(normalizedQuery, true) } return if (pages.isEmpty()) SearchResults() else SearchResults(pages.take(2).map { diff --git a/app/src/main/java/org/wikipedia/search/SearchResultsFragment.kt b/app/src/main/java/org/wikipedia/search/SearchResultsFragment.kt index 7e2bda8f6aa..46cce6cc2e6 100644 --- a/app/src/main/java/org/wikipedia/search/SearchResultsFragment.kt +++ b/app/src/main/java/org/wikipedia/search/SearchResultsFragment.kt @@ -154,9 +154,7 @@ class SearchResultsFragment : Fragment() { private inner class SearchResultsDiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: SearchResult, newItem: SearchResult): Boolean { - return oldItem.pageTitle.prefixedText == newItem.pageTitle.prefixedText && - oldItem.pageTitle.namespace == newItem.pageTitle.namespace && - oldItem.pageTitle.description == newItem.pageTitle.description + return false } override fun areContentsTheSame(oldItem: SearchResult, newItem: SearchResult): Boolean { diff --git a/app/src/main/java/org/wikipedia/search/SearchResultsViewModel.kt b/app/src/main/java/org/wikipedia/search/SearchResultsViewModel.kt index dac1ffd363d..7f3b5b8b117 100644 --- a/app/src/main/java/org/wikipedia/search/SearchResultsViewModel.kt +++ b/app/src/main/java/org/wikipedia/search/SearchResultsViewModel.kt @@ -12,7 +12,6 @@ import org.wikipedia.dataclient.ServiceFactory import org.wikipedia.dataclient.WikiSite import org.wikipedia.dataclient.mwapi.MwQueryResponse import org.wikipedia.util.StringUtil -import java.util.* class SearchResultsViewModel : ViewModel() { @@ -45,16 +44,17 @@ class SearchResultsViewModel : ViewModel() { private val languageCode: String?, private var resultsCount: MutableList?, private var totalResults: MutableList? - ) : PagingSource() { + ) : PagingSource() { private var prefixSearch = true - override suspend fun load(params: LoadParams): LoadResult { + override suspend fun load(params: LoadParams): LoadResult { return try { if (searchTerm.isNullOrEmpty() || languageCode.isNullOrEmpty()) { return LoadResult.Page(emptyList(), null, null) } + var continuation: Int? = null val wikiSite = WikiSite.forLanguageCode(languageCode) var response: MwQueryResponse? = null val resultList = mutableListOf() @@ -72,7 +72,8 @@ class SearchResultsViewModel : ViewModel() { } } } - response = ServiceFactory.get(wikiSite).prefixSearch(searchTerm, params.loadSize, params.key?.gpsoffset) + response = ServiceFactory.get(wikiSite).prefixSearch(searchTerm, params.loadSize, 0) + continuation = 0 prefixSearch = false } @@ -81,10 +82,9 @@ class SearchResultsViewModel : ViewModel() { } ?: emptyList()) if (resultList.size < params.loadSize) { - // Prevent using continuation string from prefix search after the first round of LoadResult. - val continuation = if (params.key?.continuation?.contains("description") == true) null else params.key?.continuation response = ServiceFactory.get(wikiSite) - .fullTextSearch(searchTerm, params.key?.gsroffset?.toString(), params.loadSize, continuation) + .fullTextSearch(searchTerm, params.loadSize, params.key) + continuation = response.continuation?.gsroffset resultList.addAll(response.query?.pages?.let { list -> list.sortedBy { it.index }.map { SearchResult(it, wikiSite) } @@ -105,7 +105,7 @@ class SearchResultsViewModel : ViewModel() { } if (countResultSize == 0) { val fullTextSearchResponse = ServiceFactory.get(WikiSite.forLanguageCode(langCode)) - .fullTextSearch(searchTerm, null, params.loadSize, null) + .fullTextSearch(searchTerm, params.loadSize, null) countResultSize = fullTextSearchResponse.query?.pages?.size ?: 0 } resultsCount?.add(countResultSize) @@ -117,13 +117,13 @@ class SearchResultsViewModel : ViewModel() { } } - return LoadResult.Page(resultList.distinctBy { it.pageTitle.prefixedText }, null, response?.continuation) + return LoadResult.Page(resultList.distinctBy { it.pageTitle.prefixedText }, null, continuation) } catch (e: Exception) { LoadResult.Error(e) } } - override fun getRefreshKey(state: PagingState): MwQueryResponse.Continuation? { + override fun getRefreshKey(state: PagingState): Int? { prefixSearch = true totalResults?.clear() return null @@ -133,9 +133,7 @@ class SearchResultsViewModel : ViewModel() { if (searchTerm.length >= 2) { WikipediaApp.instance.tabList.forEach { tab -> tab.backStackPositionTitle?.let { - if (StringUtil.fromHtml(it.displayText).toString() - .lowercase(Locale.getDefault()) - .contains(searchTerm.lowercase(Locale.getDefault()))) { + if (StringUtil.fromHtml(it.displayText).contains(searchTerm, true)) { return SearchResults(mutableListOf(SearchResult(it, SearchResult.SearchResultType.TAB_LIST))) } } diff --git a/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsRecentEditsFilterTypes.kt b/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsRecentEditsFilterTypes.kt index 9fa6d7cfccf..61615d7f146 100644 --- a/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsRecentEditsFilterTypes.kt +++ b/app/src/main/java/org/wikipedia/suggestededits/SuggestedEditsRecentEditsFilterTypes.kt @@ -3,7 +3,6 @@ package org.wikipedia.suggestededits import androidx.annotation.StringRes import org.wikipedia.R import org.wikipedia.model.EnumCode -import org.wikipedia.model.EnumCodeMap @Suppress("unused") enum class SuggestedEditsRecentEditsFilterTypes constructor(val id: String, @@ -80,14 +79,8 @@ enum class SuggestedEditsRecentEditsFilterTypes constructor(val id: String, val DEFAULT_FILTER_TYPE_SET = DEFAULT_FILTER_OTHERS + DEFAULT_FILTER_USER_STATUS - private val MAP = EnumCodeMap(SuggestedEditsRecentEditsFilterTypes::class.java) - - private fun findOrNull(id: String): SuggestedEditsRecentEditsFilterTypes? { - return MAP.valueIterator().asSequence().firstOrNull { id == it.id || id.startsWith(it.id) } - } - fun find(id: String): SuggestedEditsRecentEditsFilterTypes { - return findOrNull(id) ?: MAP[0] + return entries.firstOrNull { id == it.id || id.startsWith(it.id) } ?: entries[0] } fun findGroup(id: String): List { diff --git a/app/src/main/java/org/wikipedia/talk/TalkTopicActivity.kt b/app/src/main/java/org/wikipedia/talk/TalkTopicActivity.kt index 36a05a67195..3255353a88d 100644 --- a/app/src/main/java/org/wikipedia/talk/TalkTopicActivity.kt +++ b/app/src/main/java/org/wikipedia/talk/TalkTopicActivity.kt @@ -29,10 +29,19 @@ import org.wikipedia.edit.EditSectionActivity import org.wikipedia.history.HistoryEntry import org.wikipedia.history.SearchActionModeCallback import org.wikipedia.login.LoginActivity -import org.wikipedia.page.* +import org.wikipedia.page.LinkHandler +import org.wikipedia.page.LinkMovementMethodExt +import org.wikipedia.page.PageTitle import org.wikipedia.settings.Prefs import org.wikipedia.staticdata.UserAliasData -import org.wikipedia.util.* +import org.wikipedia.util.DeviceUtil +import org.wikipedia.util.FeedbackUtil +import org.wikipedia.util.L10nUtil +import org.wikipedia.util.Resource +import org.wikipedia.util.ResourceUtil +import org.wikipedia.util.ShareUtil +import org.wikipedia.util.StringUtil +import org.wikipedia.util.UriUtil import org.wikipedia.views.SearchActionProvider import org.wikipedia.views.ViewUtil diff --git a/app/src/main/java/org/wikipedia/talk/UserTalkPopupHelper.kt b/app/src/main/java/org/wikipedia/talk/UserTalkPopupHelper.kt index a5d5320a0dd..8c27754fc13 100644 --- a/app/src/main/java/org/wikipedia/talk/UserTalkPopupHelper.kt +++ b/app/src/main/java/org/wikipedia/talk/UserTalkPopupHelper.kt @@ -72,7 +72,7 @@ object UserTalkPopupHelper { helper.show() } else { ExclusiveBottomSheetPresenter.show(activity.supportFragmentManager, - LinkPreviewDialog.newInstance(HistoryEntry(title, historySource), null)) + LinkPreviewDialog.newInstance(HistoryEntry(title, historySource))) } } diff --git a/app/src/main/java/org/wikipedia/theme/Theme.kt b/app/src/main/java/org/wikipedia/theme/Theme.kt index 0f34135f303..8a863cd4c57 100644 --- a/app/src/main/java/org/wikipedia/theme/Theme.kt +++ b/app/src/main/java/org/wikipedia/theme/Theme.kt @@ -28,7 +28,7 @@ enum class Theme(val marshallingId: Int, val tag: String, @field:StyleRes @get:S get() = LIGHT fun ofMarshallingId(id: Int): Theme? { - return values().find { it.marshallingId == id } + return entries.find { it.marshallingId == id } } } } diff --git a/app/src/main/java/org/wikipedia/util/GeoUtil.kt b/app/src/main/java/org/wikipedia/util/GeoUtil.kt index fa8786de929..98a97a6a7aa 100644 --- a/app/src/main/java/org/wikipedia/util/GeoUtil.kt +++ b/app/src/main/java/org/wikipedia/util/GeoUtil.kt @@ -8,6 +8,8 @@ import android.net.Uri import org.wikipedia.R import org.wikipedia.feed.announcement.GeoIPCookieUnmarshaller import org.wikipedia.settings.Prefs +import java.text.DecimalFormat +import java.util.Locale object GeoUtil { @Suppress("UnsafeImplicitIntentLaunch") @@ -37,4 +39,16 @@ object GeoUtil { // For our purposes, don't care about malformations in the GeoIP cookie for now. null } + + fun getDistanceWithUnit(startLocation: Location, endLocation: Location, locale: Locale): String { + val countriesUsingMiles = listOf("US", "GB", "LR", "MM") + val milesInKilometers = 0.62137119 + val distance = startLocation.distanceTo(endLocation) / 1000.0 // in Kilometers + val formatter = DecimalFormat("#.##") + return if (countriesUsingMiles.contains(locale.country)) { + "${formatter.format(distance * milesInKilometers)} mi" + } else { + "${formatter.format(distance)} km" + } + } } diff --git a/app/src/main/java/org/wikipedia/util/StringUtil.kt b/app/src/main/java/org/wikipedia/util/StringUtil.kt index 078c8f7ca64..56ec753d5d6 100644 --- a/app/src/main/java/org/wikipedia/util/StringUtil.kt +++ b/app/src/main/java/org/wikipedia/util/StringUtil.kt @@ -30,7 +30,7 @@ import kotlin.math.roundToInt object StringUtil { private const val CSV_DELIMITER = "," - private val HIGHLIGHT_REGEX_OPTIONS = EnumSet.of(RegexOption.LITERAL, RegexOption.IGNORE_CASE) + val SEARCH_REGEX_OPTIONS: Set = EnumSet.of(RegexOption.LITERAL, RegexOption.IGNORE_CASE) fun listToCsv(list: List): String { return list.joinToString(CSV_DELIMITER) @@ -67,22 +67,15 @@ object StringUtil { } fun dbNameToLangCode(wikiDbName: String): String { - return (if (wikiDbName.endsWith("wiki")) wikiDbName.substring(0, wikiDbName.length - "wiki".length) else wikiDbName) - .replace("_", "-") + return wikiDbName.substringBefore("wiki").replace("_", "-") } fun removeSectionAnchor(text: String?): String { - text.orEmpty().let { - return if (it.contains("#")) it.substring(0, it.indexOf("#")) else it - } + return text.orEmpty().substringBefore('#') } fun removeNamespace(text: String): String { - return if (text.length > text.indexOf(":")) { - text.substring(text.indexOf(":") + 1) - } else { - text - } + return text.substringAfter(':') } fun removeHTMLTags(text: String?): String { @@ -148,7 +141,7 @@ object StringUtil { textView.text = if (query.isNullOrEmpty()) parentText else buildSpannedString { append(parentText) - query.toRegex(HIGHLIGHT_REGEX_OPTIONS).findAll(parentText) + query.toRegex(SEARCH_REGEX_OPTIONS).findAll(parentText) .forEach { val range = it.range val (start, end) = range.first to range.last + 1 diff --git a/app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.kt b/app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.kt index 984d8a8fefe..2e5bf71b002 100644 --- a/app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.kt +++ b/app/src/main/java/org/wikipedia/views/FaceAndColorDetectImageView.kt @@ -19,7 +19,6 @@ import com.bumptech.glide.request.target.Target import org.wikipedia.settings.Prefs import org.wikipedia.util.CenterCropWithFaceTransformation import org.wikipedia.util.WhiteBackgroundTransformation -import java.util.* class FaceAndColorDetectImageView : AppCompatImageView { @@ -34,8 +33,8 @@ class FaceAndColorDetectImageView : AppCompatImageView { private fun shouldDetectFace(uri: Uri): Boolean { // TODO: not perfect; should ideally detect based on MIME type. - val path = uri.path.orEmpty().lowercase(Locale.ROOT) - return path.endsWith(".jpg") || path.endsWith(".jpeg") + val path = uri.path.orEmpty() + return path.endsWith(".jpg", true) || path.endsWith(".jpeg", true) } fun loadImage(uri: Uri?, roundedCorners: Boolean = false, cropped: Boolean = true, emptyPlaceholder: Boolean = false, listener: OnImageLoadListener? = null) { diff --git a/app/src/main/java/org/wikipedia/views/UserMentionEditText.kt b/app/src/main/java/org/wikipedia/views/UserMentionEditText.kt index 1045eb808f9..c9ac9e29702 100644 --- a/app/src/main/java/org/wikipedia/views/UserMentionEditText.kt +++ b/app/src/main/java/org/wikipedia/views/UserMentionEditText.kt @@ -1,9 +1,9 @@ package org.wikipedia.views import android.content.Context -import android.text.SpannableStringBuilder import android.text.TextWatcher import android.util.AttributeSet +import androidx.core.text.buildSpannedString import androidx.core.widget.doOnTextChanged import org.wikipedia.dataclient.WikiSite import org.wikipedia.edit.SyntaxHighlightableEditText @@ -113,13 +113,14 @@ class UserMentionEditText : SyntaxHighlightableEditText { } fun prepopulateUserName(userName: String, wikiSite: WikiSite) { - val sb = SpannableStringBuilder() - sb.append(composeUserNameLink(userName, wikiSite)) - sb.append(" ") isUserNameCommitting = true - text = sb + val spannedString = buildSpannedString { + append(composeUserNameLink(userName, wikiSite)) + append(" ") + } + setText(spannedString) isUserNameCommitting = false - setSelection(sb.length) + setSelection(spannedString.length) } fun onCommitUserName(userName: String, wikiSite: WikiSite) { @@ -130,16 +131,18 @@ class UserMentionEditText : SyntaxHighlightableEditText { return } - val sb = SpannableStringBuilder() - sb.append(text.subSequence(0, userNameStartPos)) - sb.append(composeUserNameLink(userName, wikiSite)) - val spanEnd = sb.length - if (userNameEndPos < text.length) { - sb.append(text.subSequence(userNameEndPos, text.length - 1)) + val initialString = text.subSequence(0, userNameStartPos) + val userNameString = composeUserNameLink(userName, wikiSite) + val spannedString = buildSpannedString { + append(initialString) + append(userNameString) + if (userNameEndPos < text.length) { + append(text.subSequence(userNameEndPos, text.length - 1)) + } } - text = sb - setSelection(spanEnd) + setText(spannedString) + setSelection((initialString.toString() + userNameString).length) onCancelUserNameEntry() } finally { isUserNameCommitting = false diff --git a/app/src/main/java/org/wikipedia/views/UserMentionInputView.kt b/app/src/main/java/org/wikipedia/views/UserMentionInputView.kt index a0d39060289..018218d108b 100644 --- a/app/src/main/java/org/wikipedia/views/UserMentionInputView.kt +++ b/app/src/main/java/org/wikipedia/views/UserMentionInputView.kt @@ -83,10 +83,10 @@ class UserMentionInputView : LinearLayout, UserMentionEditText.Listener { } fun maybePrepopulateUserName(currentUserName: String, currentPageTitle: PageTitle) { - if (binding.inputEditText.text.isNullOrEmpty() && userNameHints.isNotEmpty()) { + if (binding.inputEditText.text.isEmpty() && userNameHints.isNotEmpty()) { val candidateName = userNameHints.first() if (candidateName != currentUserName && - StringUtil.addUnderscores(candidateName.lowercase()) != StringUtil.addUnderscores(currentPageTitle.text.lowercase())) { + !StringUtil.addUnderscores(candidateName).equals(StringUtil.addUnderscores(currentPageTitle.text), true)) { binding.inputEditText.prepopulateUserName(candidateName, wikiSite) } } diff --git a/app/src/main/java/org/wikipedia/watchlist/WatchlistFilterTypes.kt b/app/src/main/java/org/wikipedia/watchlist/WatchlistFilterTypes.kt index 8632639a51b..94308d00a7d 100644 --- a/app/src/main/java/org/wikipedia/watchlist/WatchlistFilterTypes.kt +++ b/app/src/main/java/org/wikipedia/watchlist/WatchlistFilterTypes.kt @@ -3,7 +3,6 @@ package org.wikipedia.watchlist import androidx.annotation.StringRes import org.wikipedia.R import org.wikipedia.model.EnumCode -import org.wikipedia.model.EnumCodeMap @Suppress("unused") enum class WatchlistFilterTypes constructor(val id: String, @@ -99,14 +98,8 @@ enum class WatchlistFilterTypes constructor(val id: String, val DEFAULT_FILTER_OTHERS = setOf(ALL_EDITS, ALL_CHANGES, LATEST_REVISION, ALL_EDITORS, ALL_USERS) val DEFAULT_FILTER_TYPE_SET = DEFAULT_FILTER_OTHERS + DEFAULT_FILTER_TYPE_OF_CHANGES - private val MAP = EnumCodeMap(WatchlistFilterTypes::class.java) - - private fun findOrNull(id: String): WatchlistFilterTypes? { - return MAP.valueIterator().asSequence().firstOrNull { id == it.id || id.startsWith(it.id) } - } - fun find(id: String): WatchlistFilterTypes { - return findOrNull(id) ?: MAP[0] + return entries.find { id == it.id || id.startsWith(it.id) } ?: entries[0] } fun findGroup(id: String): List { diff --git a/app/src/main/java/org/wikipedia/wiktionary/WiktionaryDialog.kt b/app/src/main/java/org/wikipedia/wiktionary/WiktionaryDialog.kt index da3d9dce404..8e10f74b880 100644 --- a/app/src/main/java/org/wikipedia/wiktionary/WiktionaryDialog.kt +++ b/app/src/main/java/org/wikipedia/wiktionary/WiktionaryDialog.kt @@ -140,7 +140,7 @@ class WiktionaryDialog : ExtendedBottomSheetDialogFragment() { } private fun getTermFromWikiLink(url: String): String { - return removeLinkFragment(url.substring(url.lastIndexOf("/") + 1)) + return removeLinkFragment(url.substringAfterLast('/')) } private fun removeLinkFragment(url: String): String { diff --git a/app/src/main/res/drawable/location_gradient.xml b/app/src/main/res/drawable/location_gradient.xml new file mode 100644 index 00000000000..bbd3e43e204 --- /dev/null +++ b/app/src/main/res/drawable/location_gradient.xml @@ -0,0 +1,21 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_link_preview.xml b/app/src/main/res/layout/dialog_link_preview.xml index a1304ee6ebf..e0ae9dc8c9f 100755 --- a/app/src/main/res/layout/dialog_link_preview.xml +++ b/app/src/main/res/layout/dialog_link_preview.xml @@ -35,26 +35,46 @@ android:layout_height="@dimen/defaultListItemSize" android:layout_gravity="center_vertical" android:layout_marginStart="16dp" - android:layout_marginTop="16dp" - android:layout_marginBottom="16dp" + android:layout_marginVertical="16dp" android:contentDescription="@null" android:visibility="gone" /> - + android:orientation="vertical" + android:paddingBottom="4dp"> + + + + + + + + diff --git a/app/src/main/res/layout/view_link_preview_overlay.xml b/app/src/main/res/layout/view_link_preview_overlay.xml index abe7f42c466..face4cb6073 100644 --- a/app/src/main/res/layout/view_link_preview_overlay.xml +++ b/app/src/main/res/layout/view_link_preview_overlay.xml @@ -17,14 +17,14 @@