Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2ce2e70f authored by Guillaume Jacquart's avatar Guillaume Jacquart
Browse files

Merge branch '3129-share_weeklyreport' into 'main'

3129 share weeklyreport

See merge request !190
parents f20a603d 2f19789b
Loading
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
    Copyright (C) 2025 E FOUNDATION
    Copyright  (C) 2022 ECORP, 2022 MURENA SAS

    This program is free software: you can redistribute it and/or modify
@@ -57,6 +58,14 @@
            android:enabled="true"
            android:exported="true"
            />
        <provider
            android:name=".externalinterfaces.contentproviders.ScreenshotsProvider"
            android:authorities="foundation.e.advancedprivacy.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/fileprovider_paths" />
        </provider>

        <receiver
            android:name="foundation.e.advancedprivacy.common.BootCompletedReceiver"
+57 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 E FOUNDATION
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package foundation.e.advancedprivacy.externalinterfaces.contentproviders

import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import androidx.core.content.FileProvider
import androidx.core.content.FileProvider.getUriForFile
import java.io.File

// Subclass (empty) of FileProvider, as advised in documentation
// https://developer.android.com/reference/androidx/core/content/FileProvider :
// "It is possible to use FileProvider directly instead of extending it. However, this is
// not reliable and will causes crashes on some devices."
class ScreenshotsProvider : FileProvider() {
    companion object {
        const val AUTHORITY = "foundation.e.advancedprivacy.fileprovider"
        private const val PATH_WEEKLYREPORTS = "weeklyreports"

        fun buildSendIntent(context: Context, bmp: Bitmap, message: String): Intent {
            val baseDir = File(context.cacheDir, PATH_WEEKLYREPORTS)
            baseDir.mkdirs()
            val screenshotOutputFile = File(baseDir, "lastreport.png")
            screenshotOutputFile.delete()

            val screenshotOutputStream = screenshotOutputFile.outputStream()
            bmp.compress(Bitmap.CompressFormat.PNG, 100, screenshotOutputStream)
            screenshotOutputStream.close()

            val contentUri = getUriForFile(context, AUTHORITY, screenshotOutputFile)

            return Intent().apply {
                action = Intent.ACTION_SEND
                flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
                setDataAndType(contentUri, "image/png")
                putExtra(Intent.EXTRA_TEXT, message)
                putExtra(Intent.EXTRA_STREAM, contentUri)
            }
        }
    }
}
+17 −0
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@

package foundation.e.advancedprivacy.features.debug

import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
@@ -37,6 +38,7 @@ import foundation.e.advancedprivacy.domain.entities.weeklyreport.DisplayableRepo
import foundation.e.advancedprivacy.domain.entities.weeklyreport.WeeklyReport
import foundation.e.advancedprivacy.domain.entities.weeklyreport.WeeklyReportScore
import foundation.e.advancedprivacy.domain.usecases.WeeklyReportUseCase
import foundation.e.advancedprivacy.externalinterfaces.contentproviders.ScreenshotsProvider
import foundation.e.advancedprivacy.features.weeklyreport.WeeklyReportViewFactory
import foundation.e.advancedprivacy.trackers.data.TrackersRepository
import java.time.Instant
@@ -91,6 +93,21 @@ class DebugWeeklyReportFragment : Fragment(R.layout.debug_weekly_report_fragment
                    }
                }"
            }

            holder.binding.share.setOnClickListener {
                val bmp = report.first?.let {
                    reportsFactory.createShareBmp(requireContext(), it)
                }
                if (bmp == null) return@setOnClickListener

                val sendIntent = ScreenshotsProvider.buildSendIntent(
                    requireContext(),
                    bmp,
                    getString(R.string.weeklyreport_share_message) + " " + getString(R.string.weeklyreport_share_privacy_guide)
                )

                startActivity(Intent.createChooser(sendIntent, null))
            }
        }
    }

+25 −10
Original line number Diff line number Diff line
@@ -43,6 +43,8 @@ import foundation.e.advancedprivacy.common.extensions.findViewHolderForAdapterPo
import foundation.e.advancedprivacy.common.extensions.safeNavigate
import foundation.e.advancedprivacy.common.extensions.updatePagerHeightForChild
import foundation.e.advancedprivacy.databinding.FragmentTrackersBinding
import foundation.e.advancedprivacy.domain.entities.weeklyreport.DisplayableReport
import foundation.e.advancedprivacy.externalinterfaces.contentproviders.ScreenshotsProvider
import foundation.e.advancedprivacy.features.weeklyreport.WeeklyReportViewFactory
import kotlin.getValue
import kotlinx.coroutines.flow.SharedFlow
@@ -82,6 +84,19 @@ class TrackersFragment : NavToolbarFragment(R.layout.fragment_trackers) {
            }
        })

        binding.weeklyreportShareBtn.setOnClickListener {
            viewModel.state.value?.let { report ->
                val bmp = weeklyReportViewFactory.createShareBmp(requireContext(), report)
                val sendIntent = ScreenshotsProvider.buildSendIntent(
                    requireContext(),
                    bmp,
                    getString(R.string.weeklyreport_share_message) + " " + getString(R.string.weeklyreport_share_privacy_guide)
                )

                startActivity(Intent.createChooser(sendIntent, null))
            }
        }

        listenViewModel()
    }

@@ -112,16 +127,7 @@ class TrackersFragment : NavToolbarFragment(R.layout.fragment_trackers) {

            lifecycleScope.launch {
                repeatOnLifecycle(Lifecycle.State.STARTED) {
                    viewModel.state.collect { report ->
                        if (report != null) {
                            weeklyReportViewFactory.createView(report, layoutInflater, binding.weeklyreportContainer)?.let {
                                binding.weeklyreportContainer.addView(it)
                                binding.weeklyreportContainer.isVisible = true
                            }
                        } else {
                            binding.weeklyreportContainer.isVisible = false
                        }
                    }
                    viewModel.state.collect(::render)
                }
            }

@@ -192,6 +198,15 @@ class TrackersFragment : NavToolbarFragment(R.layout.fragment_trackers) {
            }
    }

    private fun render(report: DisplayableReport?) {
        binding.weeklyreport.isVisible = report != null

        if (report != null) {
            binding.weeklyreportContainer.addView(weeklyReportViewFactory.createView(report, layoutInflater, binding.weeklyreportContainer))
            binding.weeklyreportShareTitle.text = weeklyReportViewFactory.getShareTitle(report)
        }
    }

    private fun displayToast(message: String) {
        Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show()
    }
+50 −2
Original line number Diff line number Diff line
@@ -17,23 +17,71 @@

package foundation.e.advancedprivacy.features.weeklyreport

import android.content.Context
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.Drawable
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.View.MeasureSpec
import android.view.ViewGroup
import foundation.e.advancedprivacy.databinding.WeeklyReportItemTextBinding
import foundation.e.advancedprivacy.databinding.WeeklyReportShareTemplateBinding
import foundation.e.advancedprivacy.domain.entities.weeklyreport.DisplayableReport

class WeeklyReportViewFactory() {
    fun createView(report: DisplayableReport, inflater: LayoutInflater, viewGroup: ViewGroup): View? {
    fun getShareTitle(report: DisplayableReport): CharSequence {
        return report.report.statType.name
    }

    fun createView(report: DisplayableReport, inflater: LayoutInflater, viewGroup: ViewGroup): View {
        val binding = WeeklyReportItemTextBinding.inflate(inflater, viewGroup, false)

        binding.title.text = getTitle(report)
        binding.title.text = report.report.labelId.name
        binding.description.text = getDescription(report)
        return binding.root
    }

    fun createShareBmp(context: Context, report: DisplayableReport): Bitmap {
        val screenshotWidthPx = 1080 // expected Meta width, 360dp with density x3.0
        val screenshotDensity = DisplayMetrics.DENSITY_XXHIGH // x3.0
        val screenshotFontScale = 1f // base value, 1sp == 1dp
        val screenshotUiMode = Configuration.UI_MODE_NIGHT_NO // Light mode.

        val configuration = context.resources.configuration
        configuration.uiMode = screenshotUiMode
        configuration.densityDpi = screenshotDensity
        configuration.fontScale = screenshotFontScale

        val screenshotContext = context.createConfigurationContext(configuration)
        val layoutInflater = LayoutInflater.from(screenshotContext)
        val shareTemplateBinding = WeeklyReportShareTemplateBinding.inflate(layoutInflater)
        shareTemplateBinding.container.addView(
            createView(report, layoutInflater, shareTemplateBinding.container)
        )

        val view = shareTemplateBinding.root

        view.measure(
            MeasureSpec.makeMeasureSpec(screenshotWidthPx, MeasureSpec.EXACTLY),
            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
        )

        view.layout(0, 0, view.measuredWidth, view.measuredHeight)

        val bmp = Bitmap.createBitmap(
            view.measuredWidth,
            view.measuredHeight,
            Bitmap.Config.ARGB_8888
        )
        val c = Canvas(bmp)
        view.draw(c)

        return bmp
    }

    private fun getTitle(report: DisplayableReport): CharSequence {
        return report.report.labelId.name
    }
Loading