From f05b39736c08d230d198a95600021d711be5eba4 Mon Sep 17 00:00:00 2001
From: Kacper Ziubryniewicz <kapi2289@gmail.com>
Date: Mon, 6 Jan 2020 00:11:03 +0100
Subject: [PATCH] [Dialogs/GenerateBlockTimetable] Add new dialog.

---
 .../timetable/GenerateBlockTimetableDialog.kt | 355 ++++++++++++++++++
 .../modules/timetable/v2/TimetableFragment.kt |  19 +-
 .../edziennik/utils/models/Date.java          |   4 +
 .../dialog_generate_block_timetable.xml       |  62 +++
 app/src/main/res/values/strings.xml           |   3 +
 5 files changed, 426 insertions(+), 17 deletions(-)
 create mode 100644 app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/GenerateBlockTimetableDialog.kt
 create mode 100644 app/src/main/res/layout/dialog_generate_block_timetable.xml

diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/GenerateBlockTimetableDialog.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/GenerateBlockTimetableDialog.kt
new file mode 100644
index 00000000..978030b5
--- /dev/null
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/dialogs/timetable/GenerateBlockTimetableDialog.kt
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) Kacper Ziubryniewicz 2020-1-5
+ */
+
+package pl.szczodrzynski.edziennik.ui.dialogs.timetable
+
+import android.content.Intent
+import android.graphics.*
+import android.net.Uri
+import android.os.Build
+import android.os.Environment
+import android.util.Log
+import android.view.View.MeasureSpec
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import androidx.cardview.widget.CardView
+import androidx.core.content.FileProvider
+import com.google.android.material.datepicker.MaterialDatePicker
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import kotlinx.coroutines.*
+import pl.szczodrzynski.edziennik.*
+import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
+import pl.szczodrzynski.edziennik.data.db.modules.timetable.LessonFull
+import pl.szczodrzynski.edziennik.databinding.DialogGenerateBlockTimetableBinding
+import pl.szczodrzynski.edziennik.utils.models.Date
+import pl.szczodrzynski.edziennik.utils.models.Time
+import pl.szczodrzynski.edziennik.utils.models.Week
+import java.io.File
+import java.io.FileOutputStream
+import kotlin.coroutines.CoroutineContext
+import kotlin.math.roundToInt
+
+class GenerateBlockTimetableDialog(
+        val activity: AppCompatActivity,
+        val onShowListener: ((tag: String) -> Unit)? = null,
+        val onDismissListener: ((tag: String) -> Unit)? = null
+) : CoroutineScope {
+    companion object {
+        const val TAG = "GenerateBlockTimetableDialog"
+
+        private const val WIDTH_CONSTANT = 70
+        private const val WIDTH_WEEKDAY = 285
+        private const val WIDTH_SPACING = 15
+        private const val HEIGHT_CONSTANT = 60
+        private const val HEIGHT_MINUTE = 3
+        private const val HEIGHT_FOOTER = 40
+    }
+
+    private val heightProfileName by lazy { if (showProfileName) 100 else 0 }
+
+    private val app by lazy { activity.application as App }
+
+    private lateinit var job: Job
+    override val coroutineContext: CoroutineContext
+        get() = job + Dispatchers.Main
+
+    private lateinit var dialog: AlertDialog
+    private lateinit var b: DialogGenerateBlockTimetableBinding
+
+    private var showProfileName: Boolean = false
+    private var noColors: Boolean = false
+
+    init { run {
+        if (activity.isFinishing)
+            return@run
+        job = Job()
+        onShowListener?.invoke(TAG)
+
+        val weekCurrentStart = Week.getWeekStart()
+        val weekCurrentEnd = Week.getWeekEnd()
+        val weekNextStart = weekCurrentEnd.clone().stepForward(0, 0, 1)
+        val weekNextEnd = weekNextStart.clone().stepForward(0, 0, 6)
+
+        b = DialogGenerateBlockTimetableBinding.inflate(activity.layoutInflater)
+
+        b.showProfileNameItem.onClick { b.showProfileNameCheckbox.trigger() }
+        b.showProfileNameCheckbox.setOnCheckedChangeListener { _, isChecked -> showProfileName = isChecked }
+
+        b.noColorsItem.onClick { b.noColorsCheckbox.trigger() }
+        b.noColorsCheckbox.setOnCheckedChangeListener { _, isChecked -> noColors = isChecked }
+
+        dialog = MaterialAlertDialogBuilder(activity)
+                .setTitle(R.string.timetable_generate_range)
+                .setItems(arrayOf(
+                        activity.getString(R.string.timetable_generate_current_week_format, weekCurrentStart.formattedStringShort, weekCurrentEnd.formattedStringShort)
+                                .asColoredSpannable(android.R.attr.textColorPrimary.resolveAttr(activity)),
+                        activity.getString(R.string.timetable_generate_next_week_format, weekNextStart.formattedStringShort, weekNextEnd.formattedStringShort)
+                                .asColoredSpannable(android.R.attr.textColorPrimary.resolveAttr(activity)),
+                        activity.getString(R.string.timetable_generate_selected_week)
+                                .asColoredSpannable(android.R.attr.textColorPrimary.resolveAttr(activity))
+                )) { dialog, which ->
+                    dialog.dismiss()
+                    when (which) {
+                        0 -> generateBlockTimetable(weekCurrentStart, weekCurrentEnd)
+                        1 -> generateBlockTimetable(weekNextStart, weekNextEnd)
+                        2 -> selectDate()
+                    }
+                }
+                .setView(b.root)
+                .setNeutralButton(R.string.cancel) { dialog, _ -> dialog.dismiss() }
+                .setOnDismissListener { onDismissListener?.invoke(TAG) }
+                .show()
+    }}
+
+    private fun selectDate() {
+        MaterialDatePicker.Builder
+                .datePicker()
+                .setSelection(Date.getToday().inMillis)
+                .build()
+                .apply {
+                    addOnPositiveButtonClickListener { dateInMillis ->
+                        dismiss()
+                        val selectedDate = Date.fromMillis(dateInMillis)
+                        generateBlockTimetable(selectedDate.weekStart, selectedDate.weekEnd)
+                    }
+                }
+                .show(activity.supportFragmentManager, "MaterialDatePicker")
+    }
+
+    private fun generateBlockTimetable(weekStart: Date, weekEnd: Date) { launch {
+        val progressDialog = MaterialAlertDialogBuilder(activity)
+                .setTitle(R.string.timetable_generate_progress_title)
+                .setMessage(R.string.timetable_generate_progress_text)
+                .show()
+
+        val weekDays = mutableListOf<MutableList<Lesson>>()
+        for (i in weekStart.weekDay..weekEnd.weekDay) {
+            weekDays.add(mutableListOf())
+        }
+
+        val allLessons = withContext(Dispatchers.Default) {
+            app.db.timetableDao().getBetweenDatesNow(weekStart, weekEnd)
+        }
+        val lessonRanges = mutableMapOf<Int, Int>()
+
+        var maxWeekDay = 5
+        var minTime: Time? = null
+        var maxTime: Time? = null
+
+        val lessons: List<LessonFull> = allLessons.mapNotNull { lesson ->
+            if (lesson.profileId != app.profile.id || lesson.type == Lesson.TYPE_NO_LESSONS
+                    || lesson.date == null || lesson.startTime == null || lesson.endTime == null)
+                return@mapNotNull null
+
+            if (lesson.date!!.weekDay > maxWeekDay)
+                maxWeekDay = lesson.date!!.weekDay
+
+            lessonRanges[lesson.startTime!!.value] = lesson.endTime!!.value
+            weekDays[lesson.date!!.weekDay].add(lesson)
+
+            if (minTime == null || lesson.startTime!! < minTime!!) {
+                minTime = lesson.startTime!!.clone()
+            }
+
+            if (maxTime == null || lesson.endTime!! > maxTime!!) {
+                maxTime = lesson.endTime!!.clone()
+            }
+
+            return@mapNotNull lesson
+        }
+
+        if (minTime == null) {
+            progressDialog.dismiss()
+            // TODO: Toast
+            return@launch
+        }
+
+        val diff = Time.diff(maxTime, minTime)
+
+        val imageWidth = WIDTH_CONSTANT + maxWeekDay * (WIDTH_WEEKDAY + WIDTH_SPACING) - WIDTH_SPACING
+        val imageHeight = heightProfileName + HEIGHT_CONSTANT + diff.inMinutes * HEIGHT_MINUTE + HEIGHT_FOOTER
+        val bitmap = Bitmap.createBitmap(imageWidth + 20, imageHeight + 30, Bitmap.Config.ARGB_8888)
+        val canvas = Canvas(bitmap)
+
+        if (noColors) canvas.drawARGB(255, 255, 255, 255)
+        else canvas.drawARGB(255, 225, 225, 225)
+
+        val paint = Paint().apply {
+            isAntiAlias = true
+            isFilterBitmap = true
+            isDither = true
+        }
+
+        lessons.forEach { lesson ->
+            val lessonLength = Time.diff(lesson.endTime, lesson.startTime)
+            val firstOffset = Time.diff(lesson.startTime, minTime)
+            val lessonWeekDay = lesson.date!!.weekDay
+
+            val left = WIDTH_CONSTANT + lessonWeekDay * (WIDTH_WEEKDAY + WIDTH_SPACING)
+            val top = heightProfileName + HEIGHT_CONSTANT + firstOffset.inMinutes * HEIGHT_MINUTE
+
+            val blockWidth = WIDTH_WEEKDAY
+            val blockHeight = lessonLength.inMinutes * HEIGHT_MINUTE
+
+            val viewWidth = 380.dp
+            val viewHeight = lessonLength.inMinutes * 4.dp
+
+            val layout = activity.layoutInflater.inflate(R.layout.row_timetable_block_item, null) as LinearLayout
+
+            val item: LinearLayout = layout.findViewById(R.id.timetableItemLayout)
+            val card: CardView = layout.findViewById(R.id.timetableItemCard)
+            val subjectName: TextView = layout.findViewById(R.id.timetableItemSubjectName)
+            val classroomName: TextView = layout.findViewById(R.id.timetableItemClassroomName)
+            val teacherName: TextView = layout.findViewById(R.id.timetableItemTeacherName)
+            val teamName: TextView = layout.findViewById(R.id.timetableItemTeamName)
+
+            if (noColors) {
+                card.setCardBackgroundColor(Color.WHITE)
+                card.cardElevation = 0f
+                item.setBackgroundResource(R.drawable.bg_rounded_16dp_outline)
+                subjectName.setTextColor(Color.BLACK)
+                classroomName.setTextColor(0xffaaaaaa.toInt())
+                teacherName.setTextColor(0xffaaaaaa.toInt())
+                teamName.setTextColor(0xffaaaaaa.toInt())
+            }
+
+            subjectName.text = lesson.subjectName ?: ""
+            classroomName.text = lesson.classroom ?: ""
+            teacherName.text = lesson.teacherName ?: ""
+            teamName.text = lesson.teamName ?: ""
+
+            when (lesson.type) {
+                Lesson.TYPE_NORMAL -> {}
+                Lesson.TYPE_CANCELLED, Lesson.TYPE_SHIFTED_SOURCE -> {
+                    card.setCardBackgroundColor(Color.BLACK)
+                    subjectName.setTextColor(Color.WHITE)
+                    subjectName.text = lesson.subjectName?.asStrikethroughSpannable() ?: ""
+                }
+                else -> {
+                    card.setCardBackgroundColor(0xff234158.toInt())
+                    subjectName.setTextColor(Color.WHITE)
+                    subjectName.setTypeface(null, Typeface.BOLD_ITALIC)
+                }
+            }
+
+            layout.isDrawingCacheEnabled = true
+            layout.measure(MeasureSpec.makeMeasureSpec(viewWidth, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(viewHeight, MeasureSpec.EXACTLY))
+            layout.layout(0, 0, layout.measuredWidth, layout.measuredHeight)
+            layout.buildDrawingCache(true)
+
+            val itemBitmap = layout.drawingCache
+            canvas.drawBitmap(itemBitmap, null, Rect(left, top, left + blockWidth, top + blockHeight), paint)
+        }
+
+        val textPaint = Paint().apply {
+            setARGB(255, 0, 0, 0)
+            textAlign = Paint.Align.CENTER
+            textSize = 30f
+            isAntiAlias = true
+            isFilterBitmap = true
+            isDither = true
+        }
+
+        for (w in 0..maxWeekDay) {
+            val x = WIDTH_CONSTANT + w * WIDTH_WEEKDAY + w * WIDTH_SPACING
+            canvas.drawText(Week.getFullDayName(w), x + (WIDTH_WEEKDAY / 2f), heightProfileName + HEIGHT_CONSTANT / 2 + 10f, textPaint)
+        }
+
+        if (showProfileName) {
+            textPaint.textSize = 50f
+            canvas.drawText("${app.profile.name} - plan lekcji, ${weekStart.formattedStringShort} - ${weekEnd.formattedStringShort}", (imageWidth + 20) / 2f, 80f, textPaint)
+        }
+
+        textPaint.apply {
+            setARGB(128, 0, 0, 0)
+            textAlign = Paint.Align.RIGHT
+            textSize = 26f
+            typeface = Typeface.create(Typeface.DEFAULT, Typeface.ITALIC)
+        }
+
+        val footerTextPaintCenter = ((textPaint.descent() + textPaint.ascent()) / 2).roundToInt()
+        canvas.drawText("Wygenerowano w aplikacji Szkolny.eu", imageWidth - 10f, imageHeight - footerTextPaintCenter - 10f, textPaint)
+
+        textPaint.apply {
+            setARGB(255, 127, 127, 127)
+            textAlign = Paint.Align.CENTER
+            textSize = 16f
+            typeface = Typeface.create(Typeface.DEFAULT, Typeface.NORMAL)
+        }
+
+        val textPaintCenter = ((textPaint.descent() + textPaint.ascent()) / 2).roundToInt()
+
+        val linePaint = Paint().apply {
+            setARGB(255, 100, 100, 100)
+            style = Paint.Style.STROKE
+            pathEffect = DashPathEffect(floatArrayOf(10f, 10f), 0f)
+            isAntiAlias = true
+            isFilterBitmap = true
+            isDither = true
+        }
+
+        val minTimeInt = ((minTime!!.value / 10000) * 60) + ((minTime!!.value / 100) % 100)
+
+        lessonRanges.forEach { (startTime, endTime) ->
+            listOf(startTime, endTime).forEach { value ->
+                val hour = value / 10000
+                val minute = (value / 100) % 100
+                val time = Time(hour, minute, 0)
+
+                val firstOffset = time.inMinutes - minTimeInt // offset in minutes
+                val top = (heightProfileName + HEIGHT_CONSTANT + firstOffset * HEIGHT_MINUTE).toFloat()
+
+                canvas.drawText(time.stringHM, WIDTH_CONSTANT / 2f, top - textPaintCenter, textPaint)
+                canvas.drawLine(WIDTH_CONSTANT.toFloat(), top, imageWidth.toFloat(), top, linePaint)
+            }
+        }
+
+        val today = Date.getToday().stringY_m_d
+        val now = Time.getNow().stringH_M_S
+
+        val outputDir = Environment.getExternalStoragePublicDirectory("Szkolny.eu").apply { mkdirs() }
+        val outputFile = File(outputDir, "plan_lekcji_${app.profile.name}_${today}_${now}.png")
+
+        try {
+            val fos = FileOutputStream(outputFile)
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
+            fos.close()
+        } catch (e: Exception) {
+            Log.e("SAVE_IMAGE", e.message, e)
+            return@launch
+        }
+
+        val uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            FileProvider.getUriForFile(activity, app.packageName + ".provider", outputFile)
+        } else {
+            Uri.parse("file://" + outputFile.absolutePath)
+        }
+
+        progressDialog.dismiss()
+        MaterialAlertDialogBuilder(activity)
+                .setTitle(R.string.timetable_generate_success_title)
+                .setMessage(R.string.timetable_generate_success_text)
+                .setPositiveButton(R.string.share) { dialog, _ ->
+                    dialog.dismiss()
+
+                    val intent = Intent(Intent.ACTION_SEND)
+                    intent.setDataAndType(null, "image/*")
+                    intent.putExtra(Intent.EXTRA_STREAM, uri)
+                    activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.share_intent)))
+                }
+                .setNegativeButton(R.string.open) { dialog, _ ->
+                    dialog.dismiss()
+
+                    val intent = Intent(Intent.ACTION_VIEW)
+                    intent.setDataAndType(uri, "image/*")
+                    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+                    activity.startActivity(intent)
+                }
+                .setNeutralButton(R.string.do_nothing) { dialog, _ -> dialog.dismiss() }
+                .show()
+    }}
+}
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/v2/TimetableFragment.kt b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/v2/TimetableFragment.kt
index 37d9e77c..63e6614d 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/v2/TimetableFragment.kt
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/ui/modules/timetable/v2/TimetableFragment.kt
@@ -26,6 +26,7 @@ import pl.szczodrzynski.edziennik.data.db.modules.timetable.Lesson
 import pl.szczodrzynski.edziennik.databinding.FragmentTimetableV2Binding
 import pl.szczodrzynski.edziennik.observeOnce
 import pl.szczodrzynski.edziennik.ui.dialogs.event.EventManualDialog
+import pl.szczodrzynski.edziennik.ui.dialogs.timetable.GenerateBlockTimetableDialog
 import pl.szczodrzynski.edziennik.utils.Themes
 import pl.szczodrzynski.edziennik.utils.models.Date
 import pl.szczodrzynski.navlib.bottomsheet.items.BottomSheetPrimaryItem
@@ -196,7 +197,7 @@ class TimetableFragment : Fragment(), CoroutineScope {
                         .withIcon(Icon2.cmd_table_large)
                         .withOnClickListener(View.OnClickListener {
                             activity.bottomSheet.close()
-                            //showBlockTimetableDialog()
+                            GenerateBlockTimetableDialog(activity)
                         }),
                 BottomSheetSeparatorItem(true),
                 BottomSheetPrimaryItem(true)
@@ -217,22 +218,6 @@ class TimetableFragment : Fragment(), CoroutineScope {
         })
     }}
 
-    /*private fun showBlockTimetableDialog() {
-        val weekCurrentStart = Week.getWeekStart()
-        val weekCurrentEnd = Week.getWeekEnd()
-        val weekNextStart = weekCurrentEnd.clone().stepForward(0, 0, 1)
-        val weekNextEnd = weekNextStart.clone().stepForward(0, 0, 6)
-
-        MaterialAlertDialogBuilder(activity)
-                .setTitle(R.string.timetable_generate_range)
-                .setMultiChoiceItems(arrayOf(
-                        getString(R.string.timetable_generate_current_week_format, weekCurrentStart.formattedStringShort, weekCurrentEnd.formattedStringShort),
-                        getString(R.string.timetable_generate_next_week_format, weekNextStart.formattedStringShort, weekNextEnd.formattedStringShort)
-                ), BooleanArray(2)) { dialog, which, isChecked ->
-
-                }
-    }*/
-
     private fun markLessonsAsSeen() = pageSelection?.let { date ->
         app.db.timetableDao().getForDate(App.profileId, date).observeOnce(this@TimetableFragment, Observer { lessons ->
             lessons.forEach { lesson ->
diff --git a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java
index 4e332863..a6c38bcb 100644
--- a/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java
+++ b/app/src/main/java/pl/szczodrzynski/edziennik/utils/models/Date.java
@@ -45,6 +45,10 @@ public class Date implements Comparable<Date> {
         return clone().stepForward(0, 0, -getWeekDay());
     }
 
+    public Date getWeekEnd() {
+        return clone().stepForward(0, 0, 6-getWeekDay());
+    }
+
     public static Date fromYmd(String dateTime) {
         return new Date(Integer.parseInt(dateTime.substring(0, 4)), Integer.parseInt(dateTime.substring(4, 6)), Integer.parseInt(dateTime.substring(6, 8)));
     }
diff --git a/app/src/main/res/layout/dialog_generate_block_timetable.xml b/app/src/main/res/layout/dialog_generate_block_timetable.xml
new file mode 100644
index 00000000..3f97733b
--- /dev/null
+++ b/app/src/main/res/layout/dialog_generate_block_timetable.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (c) Kacper Ziubryniewicz 2020-1-5
+  -->
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <LinearLayout
+            android:id="@+id/showProfileNameItem"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="?selectableItemBackground"
+            android:clickable="true"
+            android:focusable="true"
+            android:orientation="horizontal">
+
+            <CheckBox
+                android:id="@+id/showProfileNameCheckbox"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="20dp"
+                android:layout_marginLeft="20dp"
+                android:layout_marginTop="4dp"
+                android:layout_marginBottom="4dp" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="@string/timetable_generate_show_profile_name" />
+        </LinearLayout>
+
+        <LinearLayout
+            android:id="@+id/noColorsItem"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:background="?selectableItemBackground"
+            android:clickable="true"
+            android:focusable="true"
+            android:orientation="horizontal">
+
+            <CheckBox
+                android:id="@+id/noColorsCheckbox"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginStart="20dp"
+                android:layout_marginLeft="20dp"
+                android:layout_marginTop="4dp"
+                android:layout_marginBottom="4dp" />
+
+            <TextView
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center_vertical"
+                android:text="@string/timetable_generate_no_colors" />
+        </LinearLayout>
+    </LinearLayout>
+</layout>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index ff039368..ef4fb77d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1148,4 +1148,7 @@
     <string name="messages_compose_recipient_exists">Ten odbiorca został już wybrany</string>
     <string name="login_account_no_students">To konto nie ma żadnych uczniów</string>
     <string name="login_account_no_students_text">Do tego konta nie ma przypisanego żadnego ucznia, dlatego zalogowanie nie jest możliwe.\n\nPrzypisz ucznia na stronie swojego e-dziennika lub zaloguj się kontem, które ma przypisanego ucznia.</string>
+    <string name="timetable_generate_show_profile_name">Pokaż nazwę profilu</string>
+    <string name="timetable_generate_no_colors">Do wydruku (czarno-białe)</string>
+    <string name="timetable_generate_selected_week">Na wybrany tydzień</string>
 </resources>