mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Merge branch 'main' into explore_migration_kotlin
This commit is contained in:
commit
f29e29fd13
10 changed files with 409 additions and 71 deletions
84
.github/workflows/android-ci-comment.yml
vendored
Normal file
84
.github/workflows/android-ci-comment.yml
vendored
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
name: Android CI Comment
|
||||||
|
|
||||||
|
on: [pull_request_target]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
comment:
|
||||||
|
name: Comment on PR with APK links
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout base branch
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: ${{ github.base_ref }}
|
||||||
|
|
||||||
|
- name: Download Run ID Artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: run-id
|
||||||
|
|
||||||
|
- name: Read Run ID
|
||||||
|
id: read-run-id
|
||||||
|
run: echo "RUN_ID=$(cat run_id.txt)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Comment on PR with APK download links
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
uses: actions/github-script@v6
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
try {
|
||||||
|
const token = process.env.GH_TOKEN;
|
||||||
|
if (!token) {
|
||||||
|
throw new Error('GITHUB_TOKEN is not set.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const runId = "${{ env.RUN_ID }}";
|
||||||
|
if (!runId) {
|
||||||
|
throw new Error('Run ID not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: { artifacts } } = await github.rest.actions.listWorkflowRunArtifacts({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
run_id: runId
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!artifacts || artifacts.length === 0) {
|
||||||
|
console.log('No artifacts found for this workflow run.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const betaArtifact = artifacts.find(artifact => artifact.name === "betaDebugAPK");
|
||||||
|
const prodArtifact = artifacts.find(artifact => artifact.name === "prodDebugAPK");
|
||||||
|
|
||||||
|
if (!betaArtifact || !prodArtifact) {
|
||||||
|
console.log('Could not find both Beta and Prod APK artifacts.');
|
||||||
|
console.log('Available artifacts:', artifacts.map(a => a.name).join(', '));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const betaDownloadUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/suites/${runId}/artifacts/${betaArtifact.id}`;
|
||||||
|
const prodDownloadUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/suites/${runId}/artifacts/${prodArtifact.id}`;
|
||||||
|
|
||||||
|
const commentBody = `
|
||||||
|
📱 **APK for pull request is ready to see the changes** 📱
|
||||||
|
- [Download Beta APK](${betaDownloadUrl})
|
||||||
|
- [Download Prod APK](${prodDownloadUrl})
|
||||||
|
`;
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
issue_number: context.issue.number,
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
body: commentBody
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Successfully posted comment with APK download links');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in PR comment creation:', error);
|
||||||
|
core.setFailed(`Workflow failed: ${error.message}`);
|
||||||
|
}
|
||||||
67
.github/workflows/android.yml
vendored
67
.github/workflows/android.yml
vendored
|
|
@ -3,7 +3,6 @@ name: Android CI
|
||||||
on: [push, pull_request, workflow_dispatch]
|
on: [push, pull_request, workflow_dispatch]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: write
|
|
||||||
contents: read
|
contents: read
|
||||||
actions: read
|
actions: read
|
||||||
|
|
||||||
|
|
@ -108,63 +107,13 @@ jobs:
|
||||||
name: prodDebugAPK
|
name: prodDebugAPK
|
||||||
path: app/build/outputs/apk/prod/debug/app-*.apk
|
path: app/build/outputs/apk/prod/debug/app-*.apk
|
||||||
|
|
||||||
- name: Comment on PR with APK download links
|
- name: Store Workflow Run ID
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
uses: actions/github-script@v6
|
run: echo "${{ github.run_id }}" > run_id.txt
|
||||||
|
|
||||||
|
- name: Upload Run ID as Artifact
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
name: run-id
|
||||||
script: |
|
path: run_id.txt
|
||||||
try {
|
|
||||||
const token = process.env.GITHUB_TOKEN;
|
|
||||||
if (!token) {
|
|
||||||
throw new Error('GITHUB_TOKEN is not set. Please check workflow permissions.');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const { data: { artifacts } } = await github.rest.actions.listWorkflowRunArtifacts({
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
run_id: context.runId
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!artifacts || artifacts.length === 0) {
|
|
||||||
console.log('No artifacts found for this workflow run.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const betaArtifact = artifacts.find(artifact => artifact.name === "betaDebugAPK");
|
|
||||||
const prodArtifact = artifacts.find(artifact => artifact.name === "prodDebugAPK");
|
|
||||||
|
|
||||||
if (!betaArtifact || !prodArtifact) {
|
|
||||||
console.log('Could not find both Beta and Prod APK artifacts.');
|
|
||||||
console.log('Available artifacts:', artifacts.map(a => a.name).join(', '));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const betaDownloadUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/suites/${context.runId}/artifacts/${betaArtifact.id}`;
|
|
||||||
const prodDownloadUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/suites/${context.runId}/artifacts/${prodArtifact.id}`;
|
|
||||||
|
|
||||||
const commentBody = `
|
|
||||||
📱 **APK for pull request is ready to see the changes** 📱
|
|
||||||
- [Download Beta APK](${betaDownloadUrl})
|
|
||||||
- [Download Prod APK](${prodDownloadUrl})
|
|
||||||
`;
|
|
||||||
|
|
||||||
await github.rest.issues.createComment({
|
|
||||||
issue_number: context.issue.number,
|
|
||||||
owner: context.repo.owner,
|
|
||||||
repo: context.repo.repo,
|
|
||||||
body: commentBody
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Successfully posted comment with APK download links');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error in PR comment creation:', error);
|
|
||||||
if (error.message.includes('GITHUB_TOKEN')) {
|
|
||||||
core.setFailed('Missing or invalid GITHUB_TOKEN. Please check repository secrets configuration.');
|
|
||||||
} else if (error.status === 403) {
|
|
||||||
core.setFailed('Permission denied. Please check workflow permissions in repository settings.');
|
|
||||||
} else {
|
|
||||||
core.setFailed(`Workflow failed: ${error.message}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -175,8 +175,8 @@ dependencies {
|
||||||
testImplementation "androidx.work:work-testing:$work_version"
|
testImplementation "androidx.work:work-testing:$work_version"
|
||||||
|
|
||||||
//Glide
|
//Glide
|
||||||
implementation 'com.github.bumptech.glide:glide:4.12.0'
|
implementation 'com.github.bumptech.glide:glide:4.16.0'
|
||||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
|
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
||||||
kaptTest "androidx.databinding:databinding-compiler:8.0.2"
|
kaptTest "androidx.databinding:databinding-compiler:8.0.2"
|
||||||
kaptAndroidTest "androidx.databinding:databinding-compiler:8.0.2"
|
kaptAndroidTest "androidx.databinding:databinding-compiler:8.0.2"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,41 @@ class Media constructor(
|
||||||
captions = captions,
|
captions = captions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
captions: Map<String, String>,
|
||||||
|
categories: List<String>?,
|
||||||
|
filename: String?,
|
||||||
|
fallbackDescription: String?,
|
||||||
|
author: String?,
|
||||||
|
user: String?,
|
||||||
|
dateUploaded: Date? = Date(),
|
||||||
|
license: String? = null,
|
||||||
|
licenseUrl: String? = null,
|
||||||
|
imageUrl: String? = null,
|
||||||
|
thumbUrl: String? = null,
|
||||||
|
coordinates: LatLng? = null,
|
||||||
|
descriptions: Map<String, String> = emptyMap(),
|
||||||
|
depictionIds: List<String> = emptyList(),
|
||||||
|
categoriesHiddenStatus: Map<String, Boolean> = emptyMap()
|
||||||
|
) : this(
|
||||||
|
pageId = UUID.randomUUID().toString(),
|
||||||
|
filename = filename,
|
||||||
|
fallbackDescription = fallbackDescription,
|
||||||
|
dateUploaded = dateUploaded,
|
||||||
|
author = author,
|
||||||
|
user = user,
|
||||||
|
categories = categories,
|
||||||
|
captions = captions,
|
||||||
|
license = license,
|
||||||
|
licenseUrl = licenseUrl,
|
||||||
|
imageUrl = imageUrl,
|
||||||
|
thumbUrl = thumbUrl,
|
||||||
|
coordinates = coordinates,
|
||||||
|
descriptions = descriptions,
|
||||||
|
depictionIds = depictionIds,
|
||||||
|
categoriesHiddenStatus = categoriesHiddenStatus
|
||||||
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets media display title
|
* Gets media display title
|
||||||
* @return Media title
|
* @return Media title
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import android.view.KeyEvent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.view.ViewTreeObserver
|
||||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
|
@ -406,8 +407,13 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
|
||||||
* of the picture.
|
* of the picture.
|
||||||
*/
|
*/
|
||||||
view.post{
|
view.post{
|
||||||
|
val width = binding.mediaDetailScrollView.width
|
||||||
|
if (width > 0) {
|
||||||
frameLayoutHeight = binding.mediaDetailFrameLayout.measuredHeight
|
frameLayoutHeight = binding.mediaDetailFrameLayout.measuredHeight
|
||||||
updateAspectRatio(binding.mediaDetailScrollView.width)
|
updateAspectRatio(width)
|
||||||
|
} else {
|
||||||
|
view.postDelayed({ updateAspectRatio(binding.root.width) }, 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return view
|
return view
|
||||||
|
|
|
||||||
|
|
@ -185,10 +185,12 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
||||||
* or a fragment
|
* or a fragment
|
||||||
*/
|
*/
|
||||||
private void initProvider() {
|
private void initProvider() {
|
||||||
if (getParentFragment() != null) {
|
if (getParentFragment() instanceof MediaDetailProvider) {
|
||||||
provider = (MediaDetailProvider) getParentFragment();
|
provider = (MediaDetailProvider) getParentFragment();
|
||||||
} else {
|
} else if (getActivity() instanceof MediaDetailProvider) {
|
||||||
provider = (MediaDetailProvider) getActivity();
|
provider = (MediaDetailProvider) getActivity();
|
||||||
|
} else {
|
||||||
|
throw new ClassCastException("Parent must implement MediaDetailProvider");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,11 +39,16 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.LifecycleCoroutineScope
|
import androidx.lifecycle.LifecycleCoroutineScope
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.DividerItemDecoration
|
import androidx.recyclerview.widget.DividerItemDecoration
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
|
import com.bumptech.glide.request.RequestListener
|
||||||
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
|
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
|
@ -51,6 +56,7 @@ import com.jakewharton.rxbinding2.view.RxView
|
||||||
import com.jakewharton.rxbinding3.appcompat.queryTextChanges
|
import com.jakewharton.rxbinding3.appcompat.queryTextChanges
|
||||||
import fr.free.nrw.commons.CommonsApplication
|
import fr.free.nrw.commons.CommonsApplication
|
||||||
import fr.free.nrw.commons.MapController.NearbyPlacesInfo
|
import fr.free.nrw.commons.MapController.NearbyPlacesInfo
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.Utils
|
import fr.free.nrw.commons.Utils
|
||||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
|
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
|
||||||
|
|
@ -67,6 +73,10 @@ import fr.free.nrw.commons.location.LocationPermissionsHelper.LocationPermission
|
||||||
import fr.free.nrw.commons.location.LocationServiceManager
|
import fr.free.nrw.commons.location.LocationServiceManager
|
||||||
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType
|
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType
|
||||||
import fr.free.nrw.commons.location.LocationUpdateListener
|
import fr.free.nrw.commons.location.LocationUpdateListener
|
||||||
|
import fr.free.nrw.commons.media.MediaClient
|
||||||
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment
|
||||||
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider
|
||||||
|
import fr.free.nrw.commons.navtab.NavTab
|
||||||
import fr.free.nrw.commons.nearby.BottomSheetAdapter
|
import fr.free.nrw.commons.nearby.BottomSheetAdapter
|
||||||
import fr.free.nrw.commons.nearby.BottomSheetAdapter.ItemClickListener
|
import fr.free.nrw.commons.nearby.BottomSheetAdapter.ItemClickListener
|
||||||
import fr.free.nrw.commons.nearby.CheckBoxTriStates
|
import fr.free.nrw.commons.nearby.CheckBoxTriStates
|
||||||
|
|
@ -118,17 +128,26 @@ import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
import java.io.FileOutputStream
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.net.URLDecoder
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.UUID
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Named
|
import javax.inject.Named
|
||||||
|
import javax.sql.DataSource
|
||||||
import kotlin.concurrent.Volatile
|
import kotlin.concurrent.Volatile
|
||||||
|
|
||||||
|
|
||||||
class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmentContract.View,
|
class NearbyParentFragment : CommonsDaggerSupportFragment(),
|
||||||
WikidataP18EditListener, LocationUpdateListener, LocationPermissionCallback, ItemClickListener {
|
NearbyParentFragmentContract.View,
|
||||||
|
WikidataP18EditListener,
|
||||||
|
LocationUpdateListener,
|
||||||
|
LocationPermissionCallback,
|
||||||
|
ItemClickListener,
|
||||||
|
MediaDetailPagerFragment.MediaDetailProvider {
|
||||||
var binding: FragmentNearbyParentBinding? = null
|
var binding: FragmentNearbyParentBinding? = null
|
||||||
|
|
||||||
val mapEventsOverlay: MapEventsOverlay = MapEventsOverlay(object : MapEventsReceiver {
|
val mapEventsOverlay: MapEventsOverlay = MapEventsOverlay(object : MapEventsReceiver {
|
||||||
|
|
@ -163,6 +182,13 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
|
||||||
@Named("default_preferences")
|
@Named("default_preferences")
|
||||||
lateinit var applicationKvStore: JsonKvStore
|
lateinit var applicationKvStore: JsonKvStore
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var mediaClient: MediaClient
|
||||||
|
|
||||||
|
lateinit var mediaDetails: MediaDetailPagerFragment
|
||||||
|
|
||||||
|
lateinit var media: Media
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var bookmarkLocationDao: BookmarkLocationsDao
|
lateinit var bookmarkLocationDao: BookmarkLocationsDao
|
||||||
|
|
||||||
|
|
@ -716,6 +742,10 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
|
||||||
presenter?.attachView(this)
|
presenter?.attachView(this)
|
||||||
registerNetworkReceiver()
|
registerNetworkReceiver()
|
||||||
|
|
||||||
|
binding?.coordinatorLayout?.visibility = View.VISIBLE
|
||||||
|
binding?.map?.setMultiTouchControls(true)
|
||||||
|
binding?.map?.isClickable = true
|
||||||
|
|
||||||
if (isResumed && (activity as? MainActivity)?.activeFragment == ActiveFragment.NEARBY) {
|
if (isResumed && (activity as? MainActivity)?.activeFragment == ActiveFragment.NEARBY) {
|
||||||
if (activity?.let { locationPermissionsHelper?.checkLocationPermission(it) } == true) {
|
if (activity?.let { locationPermissionsHelper?.checkLocationPermission(it) } == true) {
|
||||||
locationPermissionGranted()
|
locationPermissionGranted()
|
||||||
|
|
@ -1853,7 +1883,31 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
|
||||||
}
|
}
|
||||||
|
|
||||||
fun backButtonClicked(): Boolean {
|
fun backButtonClicked(): Boolean {
|
||||||
return presenter!!.backButtonClicked()
|
if (::mediaDetails.isInitialized && mediaDetails.isVisible) {
|
||||||
|
removeFragment(mediaDetails)
|
||||||
|
|
||||||
|
binding?.coordinatorLayout?.visibility = View.VISIBLE
|
||||||
|
binding?.map?.setMultiTouchControls(true)
|
||||||
|
binding?.map?.isClickable = true
|
||||||
|
|
||||||
|
val transaction = childFragmentManager.beginTransaction()
|
||||||
|
val fragmentContainer = childFragmentManager.findFragmentById(R.id.coordinator_layout)
|
||||||
|
|
||||||
|
if (fragmentContainer != null) {
|
||||||
|
transaction.show(fragmentContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.commit()
|
||||||
|
childFragmentManager.executePendingTransactions()
|
||||||
|
|
||||||
|
(activity as? MainActivity)?.showTabs()
|
||||||
|
(activity as? MainActivity)?.supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
(activity as? MainActivity)?.setSelectedItemId(NavTab.NEARBY.code())
|
||||||
|
}
|
||||||
|
|
||||||
|
return presenter?.backButtonClicked() ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLocationPermissionDenied(toastMessage: String) {
|
override fun onLocationPermissionDenied(toastMessage: String) {
|
||||||
|
|
@ -2299,7 +2353,61 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
|
||||||
bottomSheetAdapter!!.setClickListener(this)
|
bottomSheetAdapter!!.setClickListener(this)
|
||||||
binding!!.bottomSheetDetails.bottomSheetRecyclerView.adapter = bottomSheetAdapter
|
binding!!.bottomSheetDetails.bottomSheetRecyclerView.adapter = bottomSheetAdapter
|
||||||
updateBookmarkButtonImage(selectedPlace!!)
|
updateBookmarkButtonImage(selectedPlace!!)
|
||||||
|
|
||||||
|
selectedPlace?.pic?.substringAfterLast("/")?.takeIf { it.isNotEmpty() }?.let { imageName ->
|
||||||
|
Glide.with(binding!!.bottomSheetDetails.icon.context)
|
||||||
|
.clear(binding!!.bottomSheetDetails.icon)
|
||||||
|
|
||||||
|
val loadingDrawable = ContextCompat.getDrawable(
|
||||||
|
binding!!.bottomSheetDetails.icon.context,
|
||||||
|
R.drawable.loading_icon
|
||||||
|
)
|
||||||
|
val animation = AnimationUtils.loadAnimation(
|
||||||
|
binding!!.bottomSheetDetails.icon.context,
|
||||||
|
R.anim.rotate
|
||||||
|
)
|
||||||
|
|
||||||
|
Glide.with(binding!!.bottomSheetDetails.icon.context)
|
||||||
|
.load("https://commons.wikimedia.org/wiki/Special:Redirect/file/$imageName?width=25")
|
||||||
|
.placeholder(loadingDrawable)
|
||||||
|
.error(selectedPlace!!.label.icon)
|
||||||
|
.listener(object : RequestListener<Drawable> {
|
||||||
|
override fun onLoadFailed(
|
||||||
|
e: GlideException?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<Drawable>,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
binding!!.bottomSheetDetails.icon.clearAnimation()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: Drawable,
|
||||||
|
model: Any,
|
||||||
|
target: Target<Drawable>?,
|
||||||
|
dataSource: com.bumptech.glide.load.DataSource,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
binding!!.bottomSheetDetails.icon.clearAnimation()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into(binding!!.bottomSheetDetails.icon)
|
||||||
|
|
||||||
|
if (binding!!.bottomSheetDetails.icon.drawable != null && binding!!.bottomSheetDetails.icon.drawable.constantState == loadingDrawable?.constantState) {
|
||||||
|
binding!!.bottomSheetDetails.icon.startAnimation(animation)
|
||||||
|
} else {
|
||||||
|
binding!!.bottomSheetDetails.icon.clearAnimation()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding!!.bottomSheetDetails.icon.setOnClickListener {
|
||||||
|
handleMediaClick(imageName)
|
||||||
|
}
|
||||||
|
} ?: run {
|
||||||
binding!!.bottomSheetDetails.icon.setImageResource(selectedPlace!!.label.icon)
|
binding!!.bottomSheetDetails.icon.setImageResource(selectedPlace!!.label.icon)
|
||||||
|
}
|
||||||
|
|
||||||
binding!!.bottomSheetDetails.title.text = selectedPlace!!.name
|
binding!!.bottomSheetDetails.title.text = selectedPlace!!.name
|
||||||
binding!!.bottomSheetDetails.category.text = selectedPlace!!.distance
|
binding!!.bottomSheetDetails.category.text = selectedPlace!!.distance
|
||||||
// Remove label since it is double information
|
// Remove label since it is double information
|
||||||
|
|
@ -2354,6 +2462,101 @@ class NearbyParentFragment : CommonsDaggerSupportFragment(), NearbyParentFragmen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleMediaClick(imageName: String) {
|
||||||
|
val decodedImageName = URLDecoder.decode(imageName, StandardCharsets.UTF_8.toString())
|
||||||
|
|
||||||
|
mediaClient.getMedia("File:$decodedImageName")
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ mediaResponse ->
|
||||||
|
if (mediaResponse != null) {
|
||||||
|
// Create a Media object from the response
|
||||||
|
media = Media(
|
||||||
|
pageId = mediaResponse.pageId ?: UUID.randomUUID().toString(),
|
||||||
|
thumbUrl = mediaResponse.thumbUrl,
|
||||||
|
imageUrl = mediaResponse.imageUrl,
|
||||||
|
filename = mediaResponse.filename,
|
||||||
|
fallbackDescription = mediaResponse.fallbackDescription,
|
||||||
|
dateUploaded = mediaResponse.dateUploaded,
|
||||||
|
license = mediaResponse.license,
|
||||||
|
licenseUrl = mediaResponse.licenseUrl,
|
||||||
|
author = mediaResponse.author,
|
||||||
|
user = mediaResponse.user,
|
||||||
|
categories = mediaResponse.categories,
|
||||||
|
coordinates = mediaResponse.coordinates,
|
||||||
|
captions = mediaResponse.captions ?: emptyMap(),
|
||||||
|
descriptions = mediaResponse.descriptions ?: emptyMap(),
|
||||||
|
depictionIds = mediaResponse.depictionIds ?: emptyList(),
|
||||||
|
categoriesHiddenStatus = mediaResponse.categoriesHiddenStatus ?: emptyMap()
|
||||||
|
)
|
||||||
|
// Remove existing fragment before showing new details
|
||||||
|
if (::mediaDetails.isInitialized && mediaDetails.isAdded) {
|
||||||
|
removeFragment(mediaDetails)
|
||||||
|
}
|
||||||
|
showMediaDetails()
|
||||||
|
} else {
|
||||||
|
Timber.e("Fetched media is null for image: $decodedImageName")
|
||||||
|
}
|
||||||
|
}, { throwable ->
|
||||||
|
Timber.e(throwable, "Error fetching media for image: $decodedImageName")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showMediaDetails() {
|
||||||
|
binding?.map?.setMultiTouchControls(false)
|
||||||
|
binding?.map?.isClickable = false
|
||||||
|
|
||||||
|
mediaDetails = MediaDetailPagerFragment.newInstance(false, true)
|
||||||
|
|
||||||
|
|
||||||
|
val transaction = childFragmentManager.beginTransaction()
|
||||||
|
|
||||||
|
val fragmentContainer = childFragmentManager.findFragmentById(R.id.coordinator_layout)
|
||||||
|
if (fragmentContainer != null) {
|
||||||
|
transaction.hide(fragmentContainer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace instead of add to ensure new fragment is used
|
||||||
|
transaction.replace(R.id.coordinator_layout, mediaDetails, "MediaDetailFragmentTag")
|
||||||
|
transaction.addToBackStack("Nearby_Parent_Fragment_Tag").commit()
|
||||||
|
childFragmentManager.executePendingTransactions()
|
||||||
|
|
||||||
|
(activity as? MainActivity)?.supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
|
||||||
|
if (mediaDetails.isAdded) {
|
||||||
|
mediaDetails.showImage(0)
|
||||||
|
} else {
|
||||||
|
Timber.e("Error: MediaDetailPagerFragment is NOT added")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMediaAtPosition(i: Int): Media? {
|
||||||
|
return media
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTotalMediaCount(): Int {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getContributionStateAt(position: Int): Int? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun refreshNominatedMedia(index: Int) {
|
||||||
|
if (this::mediaDetails.isInitialized && !binding?.map?.isClickable!! == true) {
|
||||||
|
removeFragment(mediaDetails)
|
||||||
|
showMediaDetails()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun removeFragment(fragment: Fragment) {
|
||||||
|
childFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.remove(fragment)
|
||||||
|
.commit()
|
||||||
|
childFragmentManager.executePendingTransactions()
|
||||||
|
}
|
||||||
|
|
||||||
private fun storeSharedPrefs(selectedPlace: Place) {
|
private fun storeSharedPrefs(selectedPlace: Place) {
|
||||||
applicationKvStore!!.putJson<Place>(WikidataConstants.PLACE_OBJECT, selectedPlace)
|
applicationKvStore!!.putJson<Place>(WikidataConstants.PLACE_OBJECT, selectedPlace)
|
||||||
val place =
|
val place =
|
||||||
|
|
|
||||||
10
app/src/main/res/anim/rotate.xml
Normal file
10
app/src/main/res/anim/rotate.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<rotate
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:fromDegrees="0"
|
||||||
|
android:toDegrees="360"
|
||||||
|
android:pivotX="50%"
|
||||||
|
android:pivotY="50%"
|
||||||
|
android:duration="500"
|
||||||
|
android:repeatCount="infinite"
|
||||||
|
android:interpolator="@android:anim/linear_interpolator"/>
|
||||||
49
app/src/main/res/drawable/loading_icon.xml
Normal file
49
app/src/main/res/drawable/loading_icon.xml
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<vector
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M12,4 A8,8 0 1,1 4,12 A8,8 0 1,5 19.42 ,15"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeColor="#2196F3"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:trimPathStart="0.2"
|
||||||
|
android:trimPathEnd="1.0"/>
|
||||||
|
</vector>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<vector
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M12,4 A8,8 0 1,1 4,12 A8,8 0 1,1 12,4"
|
||||||
|
android:strokeWidth="2"
|
||||||
|
android:strokeColor="#FFFFFF"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:trimPathStart="0.0"
|
||||||
|
android:trimPathEnd="0.2"/>
|
||||||
|
</vector>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
<item android:right="12dp">
|
||||||
|
<rotate
|
||||||
|
android:fromDegrees="0"
|
||||||
|
android:toDegrees="360"
|
||||||
|
android:pivotX="50%"
|
||||||
|
android:pivotY="50%">
|
||||||
|
<shape android:shape="ring">
|
||||||
|
<size
|
||||||
|
android:width="12dp"
|
||||||
|
android:height="2dp"/>
|
||||||
|
<solid android:color="#2196F3"/>
|
||||||
|
</shape>
|
||||||
|
</rotate>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue