diff --git a/Dissertation/.gitignore b/Dissertation/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/Dissertation/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/Dissertation/.idea/.gitignore b/Dissertation/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/Dissertation/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/Dissertation/.idea/.name b/Dissertation/.idea/.name new file mode 100644 index 0000000..fa908fe --- /dev/null +++ b/Dissertation/.idea/.name @@ -0,0 +1 @@ +PowerOf10 \ No newline at end of file diff --git a/Dissertation/.idea/compiler.xml b/Dissertation/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/Dissertation/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Dissertation/.idea/deploymentTargetDropDown.xml b/Dissertation/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..07bca98 --- /dev/null +++ b/Dissertation/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dissertation/.idea/gradle.xml b/Dissertation/.idea/gradle.xml new file mode 100644 index 0000000..a0de2a1 --- /dev/null +++ b/Dissertation/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/Dissertation/.idea/inspectionProfiles/Project_Default.xml b/Dissertation/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..673138f --- /dev/null +++ b/Dissertation/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,42 @@ + + + + \ No newline at end of file diff --git a/Dissertation/.idea/misc.xml b/Dissertation/.idea/misc.xml new file mode 100644 index 0000000..72b97aa --- /dev/null +++ b/Dissertation/.idea/misc.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Dissertation/.idea/vcs.xml b/Dissertation/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/Dissertation/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Dissertation/app/.gitignore b/Dissertation/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/Dissertation/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/Dissertation/app/build.gradle b/Dissertation/app/build.gradle new file mode 100644 index 0000000..a98e80b --- /dev/null +++ b/Dissertation/app/build.gradle @@ -0,0 +1,92 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + + +} +apply plugin: 'kotlin-android' + +android { + compileSdk 33 + + defaultConfig { + minSdk 28 + targetSdk 33 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary true + } + signingConfig signingConfigs.debug + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + buildFeatures { + compose true + //noinspection DataBindingWithoutKapt + dataBinding true + } + composeOptions { + kotlinCompilerExtensionVersion compose_version + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } + } + namespace 'com.example.powerof10' +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.10.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + implementation 'com.google.android.material:material:1.8.0' + implementation('androidx.compose.material3:material3:1.1.0-rc01') + implementation('androidx.compose.material3:material3-window-size-class:1.1.0-rc01') + implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' + implementation 'androidx.activity:activity-compose:1.7.1' + implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion" + implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion" + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'com.google.code.gson:gson:2.10.1' + implementation 'androidx.databinding:databinding-runtime:8.0.0' + implementation 'androidx.navigation:navigation-compose:2.6.0-beta01' + implementation 'com.github.ivanisidrowu.ktrssreader:android:v2.1.2' + implementation('io.coil-kt:coil-compose:2.3.0') + implementation 'com.prof18.rssparser:rssparser:3.1.3' + implementation 'org.jsoup:jsoup:1.15.4' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" + implementation "androidx.core:core-ktx:1.10.0" + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" + implementation 'com.github.devscast:validable:1.2.0' + implementation "com.google.accompanist:accompanist-webview:0.30.1" + implementation 'com.github.ireward:compose-html:1.0.2' + implementation("com.himanshoe:charty:1.0.1") + implementation "com.patrykandpatrick.vico:compose:1.6.5" + +} diff --git a/Dissertation/app/proguard-rules.pro b/Dissertation/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/Dissertation/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/Dissertation/app/release/app-release.apk b/Dissertation/app/release/app-release.apk new file mode 100644 index 0000000..eb29ef1 Binary files /dev/null and b/Dissertation/app/release/app-release.apk differ diff --git a/Dissertation/app/release/output-metadata.json b/Dissertation/app/release/output-metadata.json new file mode 100644 index 0000000..b8614f5 --- /dev/null +++ b/Dissertation/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.example.powerof10", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "app-release.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/Dissertation/app/src/androidTest/java/com/example/powerof10/ExampleInstrumentedTest.kt b/Dissertation/app/src/androidTest/java/com/example/powerof10/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..e988ec8 --- /dev/null +++ b/Dissertation/app/src/androidTest/java/com/example/powerof10/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.powerof10 + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.powerof10", appContext.packageName) + } +} \ No newline at end of file diff --git a/Dissertation/app/src/main/AndroidManifest.xml b/Dissertation/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a70fdc4 --- /dev/null +++ b/Dissertation/app/src/main/AndroidManifest.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dissertation/app/src/main/ic_launcher-playstore.png b/Dissertation/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..3a4a49b Binary files /dev/null and b/Dissertation/app/src/main/ic_launcher-playstore.png differ diff --git a/Dissertation/app/src/main/java/com/example/powerof10/Adapter/FeedAdapter.kt b/Dissertation/app/src/main/java/com/example/powerof10/Adapter/FeedAdapter.kt new file mode 100644 index 0000000..1b4a9b0 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/Adapter/FeedAdapter.kt @@ -0,0 +1,86 @@ +package com.example.powerof10.Adapter + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.example.powerof10.Interface.ItemClickListener +import com.example.powerof10.R + +import com.example.powerof10.Model.RSSObject +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +class FeedViewHolder(itemView:View):RecyclerView.ViewHolder(itemView),View.OnClickListener,View.OnLongClickListener { + + var txtTitle: TextView + var image: ImageView + var txtContent: TextView + + private var itemClickListener: ItemClickListener?=null + init { + txtContent = itemView.findViewById(R.id.txtContent) + txtTitle = itemView.findViewById(R.id.txtTitle) + image = itemView.findViewById(R.id.image) + + itemView.setOnClickListener(this) + itemView.setOnLongClickListener(this) + } + + fun setItemClickListener(itemClickListener: ItemClickListener) + { + this.itemClickListener = itemClickListener + } + + override fun onClick(v: View?) { + itemClickListener!!.onClick(v,adapterPosition,false) + } + + override fun onLongClick(v: View?): Boolean { + itemClickListener!!.onClick(v,adapterPosition,true) + return true + } +} +fun extractP(description: String):String{ + val doc: Document = Jsoup.parse(description) + val p : Element = doc.select("p").first()!! + return p.text() + +} +class FeedAdapter (private val rssObject: RSSObject, private val mContenxt: Context):RecyclerView.Adapter() +{ + private val inflater: LayoutInflater = LayoutInflater.from(mContenxt) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedViewHolder { + val itemView = inflater.inflate(R.layout.row,parent,false) + return FeedViewHolder(itemView) + } + + + override fun onBindViewHolder(holder: FeedViewHolder, position: Int) { + holder.txtTitle.text = rssObject.items[position].title + // holder.image.drawable = Picasso.get().load(rssObject.items[position].thumbnail).in# + holder.txtContent.text = extractP(rssObject.items[position].description) + + holder.setItemClickListener(ItemClickListener{view,position,isLongClick -> + + if(!isLongClick) + { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(rssObject.items[position].link)) + browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + mContenxt.startActivity(browserIntent) + + } + }) + } + override fun getItemCount(): Int { + return rssObject.items.size + } + +} \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/AnalysisTool.kt b/Dissertation/app/src/main/java/com/example/powerof10/AnalysisTool.kt new file mode 100644 index 0000000..700db6c --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/AnalysisTool.kt @@ -0,0 +1,236 @@ +package com.example.powerof10 + +import android.graphics.Paint +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.* +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.powerof10.ui.theme.PowerOf10Theme +import com.himanshoe.charty.common.axis.AxisConfig +import com.himanshoe.charty.line.LineChart +import com.himanshoe.charty.line.model.LineData +import com.patrykandpatrick.vico.compose.axis.axisLabelComponent +import com.patrykandpatrick.vico.compose.axis.horizontal.bottomAxis +import com.patrykandpatrick.vico.compose.axis.vertical.startAxis +import com.patrykandpatrick.vico.compose.chart.Chart +import com.patrykandpatrick.vico.compose.chart.column.columnChart +import com.patrykandpatrick.vico.compose.chart.line.lineChart +import com.patrykandpatrick.vico.compose.style.ProvideChartStyle +import com.patrykandpatrick.vico.core.axis.AxisPosition +import com.patrykandpatrick.vico.core.axis.formatter.AxisValueFormatter +import com.patrykandpatrick.vico.core.axis.horizontal.HorizontalAxis +import com.patrykandpatrick.vico.core.chart.scale.AutoScaleUp +import com.patrykandpatrick.vico.core.chart.values.AxisValuesOverrider +import com.patrykandpatrick.vico.core.entry.FloatEntry +import com.patrykandpatrick.vico.core.entry.entryModelOf +import com.patrykandpatrick.vico.sample.showcase.rememberChartStyle +import com.patrykandpatrick.vico.sample.showcase.rememberMarker +import org.jsoup.nodes.Element +import java.time.Month + +class AnalysisTool : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + PowerOf10Theme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + + } + } + } + } +} + +@Composable +fun Greeting(ldata:MutableList) { + + + Column { + + + Text(text = "Hello !") + var colors = listOf(Color.Cyan, Color.Blue) + Box(modifier = Modifier + .fillMaxWidth() + .padding(20.dp)) { + LineChart( + modifier = Modifier + .fillMaxWidth() + .height(100.dp), + color = colors.first(), + lineData = ldata + ) + LineChart( + modifier = Modifier + .fillMaxWidth() + .height(100.dp), + color = colors[1], + axisConfig = AxisConfig(false,false,false,false, textColor = Color.Black), + lineData = listOf( + LineData(10F, 0F), + LineData(20F, 23F), + LineData(10F, 0f), + LineData(80F, 0f), + LineData(10F, 0f), + LineData(50F, 50F), + LineData(20F, 35F), + ) + ) + }} + +} +@Composable +fun BestVSCountryChart (datalist:MutableMap>){ + var axis = listOf("2023","2022","2021","2020","2019","2018") + var marker = rememberMarker() + val chartEntryModel = datalist.values.first() + val bottomAxisValueFormatter = + AxisValueFormatter{ x, chartVal ->axis[x.toInt()] } + ProvideChartStyle(rememberChartStyle(chartColors = listOf(Color(0xffb983ff),Color(0xff91b1fd)))) { + Chart( + chart = lineChart( axisValuesOverrider = AxisValuesOverrider.adaptiveYValues(1f, round = false)), + model = entryModelOf(chartEntryModel, listOf(FloatEntry(0f,6.45f), FloatEntry(1f,6.56f),FloatEntry(2f,6.59f),FloatEntry(3f,6.53f),FloatEntry(4f,6.53f),FloatEntry(5f,6.53f))) , + startAxis = startAxis(), + bottomAxis = bottomAxis(valueFormatter = bottomAxisValueFormatter), + marker = marker, + modifier = Modifier.fillMaxHeight(), + autoScaleUp = AutoScaleUp.Full, + isZoomEnabled = true + ) + } + + +} +@Composable +fun BestOverSeasonChart (datalist:MutableList){ + var xaxis = mutableListOf() + var yaxis = mutableListOf() + for (index in 0 until datalist.size){ + xaxis.add(datalist[index].child(11).text()) + yaxis.add(FloatEntry(index.toFloat(),datalist[index].child(1).text().toFloat())) + + } + var marker = rememberMarker() + val chartEntryModel = entryModelOf(yaxis) + val bottomAxisValueFormatter = + AxisValueFormatter{ x, chartVal ->xaxis[x.toInt()] } + Chart( + chart = lineChart( axisValuesOverrider = AxisValuesOverrider.adaptiveYValues(1f, round = false)), + model = chartEntryModel, + startAxis = startAxis(), + bottomAxis = bottomAxis(tickPosition = + HorizontalAxis.TickPosition.Center(1, 3), valueFormatter = bottomAxisValueFormatter), + marker = marker, + //legend = rememberLegend(), + modifier = Modifier.fillMaxHeight(), + autoScaleUp = AutoScaleUp.Full, + isZoomEnabled = true + ) + +} +@Composable +fun BestByVenueChart (datalist:MutableMap>){ //for individuals + var marker = rememberMarker() + val chartEntryModel = mutableListOf() + for (index in 0 until datalist.values.flatten().size){ + chartEntryModel.add(FloatEntry(index.toFloat(),datalist.values.flatten()[index].toFloat()))// this will not work if athlete has only a dnf + } + val bottomAxisValueFormatter = + AxisValueFormatter{ x, chartVal ->datalist.keys.toList()[x.toInt()] } + Chart( + chart = columnChart( axisValuesOverrider = AxisValuesOverrider.adaptiveYValues(1.005f, round = false)), + model = entryModelOf(chartEntryModel) , + startAxis = startAxis(), + marker = marker, + bottomAxis = bottomAxis(valueFormatter = bottomAxisValueFormatter), + modifier = Modifier.fillMaxHeight(), + autoScaleUp = AutoScaleUp.Full, + isZoomEnabled = true + ) + +} + +@Composable +fun VenueByTimesChart (datalist:MutableMap>){//for country + var marker = rememberMarker() + val chartEntryModel = mutableListOf() + for (index in 0 until datalist.values.flatten().size){ + chartEntryModel.add(FloatEntry(index.toFloat(),datalist.values.flatten()[index].toFloat()))// this will not work if athlete has only a dnf + } + val bottomAxisValueFormatter = + AxisValueFormatter{ x, chartVal ->datalist.keys.toList()[x.toInt()] } + Chart( + chart = columnChart( spacing = 60.dp, axisValuesOverrider = AxisValuesOverrider.adaptiveYValues(1.005f, round = false)), + model = entryModelOf(chartEntryModel) , + startAxis = startAxis(), + marker = marker, + bottomAxis = bottomAxis( valueFormatter = bottomAxisValueFormatter , label = axisLabelComponent(lineCount = 4, textAlign = Paint.Align.CENTER, textSize = 14.sp)), + modifier = Modifier.fillMaxHeight(), + autoScaleUp = AutoScaleUp.Full, + isZoomEnabled = true + ) + +} + +@Composable +fun FastestTimeByMonthChart (datalist:MutableMap>){//for country + var marker = rememberMarker() + val chartEntryModel = mutableListOf() + for (index in 0 until datalist.values.flatten().size){ + chartEntryModel.add(FloatEntry(index.toFloat(),datalist.values.flatten()[index].toFloat()))// this will not work if athlete has only a dnf + } + val bottomAxisValueFormatter = + AxisValueFormatter{ x, chartVal ->datalist.keys.toList()[x.toInt()].toString() } + Chart( + chart = columnChart( spacing = 60.dp, axisValuesOverrider = AxisValuesOverrider.adaptiveYValues(1.005f, round = false)), + model = entryModelOf(chartEntryModel) , + startAxis = startAxis(), + marker = marker, + bottomAxis = bottomAxis( valueFormatter = bottomAxisValueFormatter , label = axisLabelComponent(lineCount = 4, textAlign = Paint.Align.CENTER, textSize = 14.sp)), + modifier = Modifier.fillMaxHeight(), + autoScaleUp = AutoScaleUp.Full, + isZoomEnabled = true + ) + +} + +@Composable +fun PBsByMonthChart (datalist: MutableMap){//for country + var marker = rememberMarker() + val chartEntryModel = mutableListOf() + for (index in 0 until datalist.values.size){ + chartEntryModel.add(FloatEntry(index.toFloat(),datalist.values.toList()[index].toFloat()))// this will not work if athlete has only a dnf + } + val bottomAxisValueFormatter = + AxisValueFormatter{ x, chartVal ->datalist.keys.toList()[x.toInt()].toString() } + Chart( + chart = columnChart( spacing = 60.dp, axisValuesOverrider = AxisValuesOverrider.adaptiveYValues(1.005f, round = true)), + model = entryModelOf(chartEntryModel) , + startAxis = startAxis(), + marker = marker, + bottomAxis = bottomAxis( valueFormatter = bottomAxisValueFormatter , label = axisLabelComponent(lineCount = 4, textAlign = Paint.Align.CENTER, textSize = 14.sp)), + modifier = Modifier.fillMaxHeight(), + autoScaleUp = AutoScaleUp.Full, + isZoomEnabled = true + ) + +} +@Preview(showBackground = true, heightDp = 500) +@Composable +fun DefaultPreview() { + PowerOf10Theme { + } +} \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/AthleteDataCollection.kt b/Dissertation/app/src/main/java/com/example/powerof10/AthleteDataCollection.kt new file mode 100644 index 0000000..772c245 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/AthleteDataCollection.kt @@ -0,0 +1,114 @@ +package com.example.powerof10 + +import com.patrykandpatrick.vico.core.entry.FloatEntry +import com.patrykandpatrick.vico.core.entry.entryModelOf +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.Month +import java.time.format.DateTimeFormatter + +class AthleteDataCollection { + val yearly60times = entryModelOf(6.45f,6.56f,6.59f,6.53f,6.53f,6.53f) + + suspend fun lifetimePerformances(html:Document): MutableMap> { + + var bestPerf = getBestPerformances(html) + var lineData= mutableMapOf >()// Example (Key:60,Value:(6.59,6.56,6.78)) + for (event in 1 until bestPerf.size){//for every event that the athlete has on their page + var performances = mutableListOf() + for(it in 0..5){//retrieves the last 5 years + if (!bestPerf.eq(event).first()?.child(it + 2)?.text()!!.replace("i","").isNullOrEmpty()){//checks if the athlete has an SB for that season + performances.add(FloatEntry(x = it.toFloat(),y=bestPerf.eq(event).first()?.child(it + 2)?.text()!!.replace("i","").replace(":","").replace("w","").substringBefore("/").toFloat()))//formats results like "6.34i" or "11.23/11.11w" to float + } else { + //performances.add(FloatEntry(0f,0f)) + } + + } + lineData.put(bestPerf.eq(event).first()?.child(0 )?.text()!!.replace("i",""), performances) + + } + + //return line + + return (lineData) + + + + } +} + + +suspend fun Overall60Season(html:Document): MutableList { + var data = getAllPerformances(html).first()!!.children() + var allPerformances60 = mutableListOf()//empty array for each performance + data.removeFirst()//removes headers + var dataList = data.sortedBy { SimpleDateFormat("dd MMM yy").parse(it.child(11).text()) } + for (performance in dataList){//in all performances + if (performance.firstElementChild()!!.text()=="60"){ + allPerformances60.add(performance)//add to the allperformances60 array if its a 60m event + } + } + + return(allPerformances60) + +} + + +suspend fun timeByVenue(html:Document): MutableMap> { + var data = getAllPerformances(html).first()!!.children() + var allPerformances60 = mutableListOf()//empty array for each performance + for (performance in data){//in all performances + if (performance.firstElementChild()!!.text()=="60"){ + allPerformances60.add(performance)//add to the allperformances60 array if its a 60m event + } + } + var grouped = allPerformances60.groupByTo (mutableMapOf(),{it.child(9).text() },{it.child(1).text()}) + for (key in grouped.keys){ + grouped[key.toString()] = mutableListOf(grouped[key.toString()]!!.min()) + } + + return(grouped) +} +suspend fun PbsByVenue(): MutableMap> { + var html = getHTML("60","All","Men","2023").getElementsByAttributeValueContaining("class", "rlr") + var grouped = html.groupByTo(mutableMapOf(),{it.child(11).text() },{it.child(1).text()}) + for (key in grouped.keys){ + grouped[key.toString()] = mutableListOf(grouped[key.toString()]!!.min()) + } + return(grouped) + +} + +suspend fun FastestByMonth(): MutableMap> { + var data = getHTML("60","All","Men","2023").getElementsByAttributeValueContaining("class", "rlr") + + var grouped = data.groupByTo(mutableMapOf(),{ LocalDate.parse(it.child(12).text(), DateTimeFormatter.ofPattern("d MMM uu")).month },{it.child(1).text()}) + for (key in grouped.keys){ + grouped[key] = mutableListOf(grouped[key]!!.min()) + } + + return(grouped) + +} +suspend fun PBsByMonth(): MutableMap { + var data = getHTML("60","All","Men","2023").getElementsByAttributeValueContaining("class", "rlr") + var PBsPerformances = mutableListOf()//empty array for each performance + data.forEach{ + if(it.child(5).firstElementChild()?.hasClass("rlpb") == true){ + PBsPerformances.add(it) + } + } + var grouped = PBsPerformances.groupingBy { LocalDate.parse(it.child(12).text(), DateTimeFormatter.ofPattern("d MMM uu")).month }.eachCount().toMutableMap() + + println(grouped.javaClass.name) + + return(grouped) + + +} +suspend fun main(){ +(PBsByMonth()) + +} \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/AthleteProfile.kt b/Dissertation/app/src/main/java/com/example/powerof10/AthleteProfile.kt new file mode 100644 index 0000000..66e318b --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/AthleteProfile.kt @@ -0,0 +1,574 @@ +package com.example.powerof10 + +//import androidx.compose.ui.platform.LocalContext +import ExpandableListViewModel +import android.annotation.SuppressLint +import android.net.Uri +import androidx.compose.animation.* +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import coil.compose.AsyncImage +import com.example.powerof10.Components.NavDrawerBody +import com.example.powerof10.ui.theme.PowerOf10Theme +import com.ireward.htmlcompose.HtmlText +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.select.Elements +import java.net.URLEncoder +import java.nio.charset.StandardCharsets + + +suspend fun getHTML(profile: String): Document { //retrieves the entire HTML page including tags using search parameters + val html= "https://www.thepowerof10.info/athletes/$profile" + + return withContext(Dispatchers.IO) { + return@withContext Jsoup.connect(html).get() + } + +} +fun getDetails(doc: Document): MutableMap { //gets athlete details + if (doc.getElementById("cphBody_pnlAthleteDetails")!!.child(1)!!.child(0).child(0).child(0).child(0).child(0).child(0).child(1).text() =="Yes"||doc.getElementById("cphBody_pnlAthleteDetails")!!.child(1)!!.child(0).child(0).child(0).child(0).child(0).child(0).child(1).text()=="No"){ + doc.getElementById("cphBody_pnlAthleteDetails")!!.child(1)!!.child(0).child(0).child(0).child(0).child(0).child(0).remove() + } +val details = mutableMapOf( + "Name" to doc.getElementsByAttributeValueContaining("class","athleteprofilesubheader").first() + !!.text(), + "Club" to doc.getElementById("cphBody_pnlAthleteDetails")!!.child(1)!!.child(0).child(0).child(0).child(0).child(0).child(0).child(1).text(), + "Gender" to doc.getElementById("cphBody_pnlAthleteDetails")!!.child(1)!!.child(0).child(0).child(0).child(0).child(0).child(1).child(1).text(), + "Age" to doc.getElementById("cphBody_pnlAthleteDetails")!!.child(1)!!.child(0).child(0).child(0).child(0).child(0).child(2).child(1).text(), + "County" to doc.getElementById("cphBody_pnlAthleteDetails")!!.child(1)!!.child(0).child(0).child(0).child(0).child(0).child(3).child(1).text(), + "Region" to doc.getElementById("cphBody_pnlAthleteDetails")!!.child(1)!!.child(0).child(0).child(0).child(0).child(0).child(4).child(1).text(), + "Nation" to doc.getElementById("cphBody_pnlAthleteDetails")!!.child(1)!!.child(0).child(0).child(0).child(0).child(0).child(5).child(1).text(), + "About" to doc.getElementById("cphBody_pnlAbout")!!.child(2)!!.child(0).child(0).child(0).toString(), + "Picture" to doc.getElementById("cphBody_lnkProfilePic")!!.attr("href"), + "Coaches" to doc.getElementById("cphBody_pnlAthleteDetails")!!.child(1)!!.child(0).child(0).child(1).child(0).toString() +) + + return details +} +fun getBestPerformances(doc: Document): Elements {//gets their performance summary by year + var result = doc.getElementById("cphBody_divBestPerformances")!!.getElementsByTag("tr") // returns the best performances elements + for (i in 1..result.size){ + if (result.eq(i).first()?.child(0)?.text().toString()=="Event"){//removes the cell containing the word event + result.removeAt(i) + + + } + + } + return result +} +fun getAllPerformances(doc: Document):Elements{//retrieves every performance + var allPerformances = Elements() + var result = doc.getElementById("cphBody_pnlPerformances")!!.getElementsByTag("tr") + var key ="" + result.forEach() {//makes the each event a child of the year they belong to + if (it.attr("style")=="background-color:DarkGray;") { + allPerformances.add(it) + } + else if (it.attr("style")=="background-color:Gainsboro;"||it.attr("style")=="background-color:WhiteSmoke;"){ + allPerformances.last()?.appendChild(it) + } + + + + } + return allPerformances +} + +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AthleteProfile(navController: NavController,viewModel: ExpandableListViewModel,athlete: String) { + val powerOfTen = painterResource(id = R.drawable.powerof10) + val profilePic = painterResource(id = R.drawable.background) + val scaffoldState = rememberScaffoldState() + var name: String by remember { mutableStateOf("") } + var club: String by remember { mutableStateOf("") } + var gender: String by remember { mutableStateOf("") } + var age: String by remember { mutableStateOf("") } + var county: String by remember { mutableStateOf("") } + var region: String by remember { mutableStateOf("") } + var nation: String by remember { mutableStateOf("") } + var about: String by remember { mutableStateOf("") } + var picture: String by remember { mutableStateOf("") } + var coaches by remember { mutableStateOf("") } + var bestPerformancesSize by remember { mutableStateOf(1) } + var bestPerformances by remember { mutableStateOf(Elements()) } + var allPerformances by remember { mutableStateOf(Elements()) } + var isRSSLoading by remember { mutableStateOf(true) } //the state of whether the rss data has loaded yet + val itemIds by viewModel.itemIds.collectAsState() + val scope = rememberCoroutineScope() + LaunchedEffect(Unit) { + + val html = getHTML(athlete) + bestPerformances = getBestPerformances(html) + allPerformances = getAllPerformances(html) + bestPerformancesSize = bestPerformances.size-1 + var details = getDetails(html) + name = details["Name"]!! + club = details["Club"]!! + gender = details["Gender"]!! + age = details["Age"]!! + county = details["County"]!! + region = details["Region"]!! + nation = details["Nation"]!! + about = details["About"]!! + picture = details["Picture"]!! + coaches = details["Coaches"]!! + isRSSLoading=false + println(html) + + } + + //var context = LocalContext.current + if (isRSSLoading) {// if the RSS hasn't loaded circular progress indicator will appear + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxSize() + ) { + CircularProgressIndicator() + } + } + if (!isRSSLoading) {// hides everything till loaded + Column { + + Scaffold( + scaffoldState = scaffoldState, + topBar = { + CenterAlignedTopAppBar(modifier = Modifier.fillMaxWidth(), + title = { + Image( + painter = powerOfTen, + contentDescription = "Power of Ten Logo", + modifier = Modifier.padding(5.dp), + contentScale = ContentScale.FillBounds + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + Color(0xFFE5383B), + navigationIconContentColor = Color.White + ), + navigationIcon = { + IconButton(onClick = { scope.launch { scaffoldState.drawerState.open() } }) { + Icon( + Icons.Filled.Menu, contentDescription = null, + tint = Color.White, + modifier = Modifier.size(48.dp) + ) + } + } + ) + }, + drawerContent = { + NavDrawerBody(items = NavigationOptions.nav, onItemClick = { navController.navigate(route = it.route) } + ) + }, content = { + + + Column( + modifier = Modifier.fillMaxWidth() + ) { + + + + LazyColumn(content = { + item { + +//Details + Text( + text = name, + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + modifier = Modifier.padding(5.dp) + ) + + + Row(modifier = Modifier + .fillMaxWidth() + .padding(bottom = 10.dp)) { + Column(modifier = Modifier.padding(horizontal = 5.dp)) { + Text( + text = club, + fontSize = 16.sp, + modifier = Modifier + .fillMaxWidth(0.4f) + ) + Text( + text = gender, + fontSize = 16.sp, + modifier = Modifier + .fillMaxWidth(0.4f) + ) + Text( + text = age, + fontSize = 16.sp, + modifier = Modifier + .fillMaxWidth(0.4f) + ) + Text( + text = region, + fontSize = 16.sp, + modifier = Modifier + .fillMaxWidth(0.4f) + ) + Text( + text = nation, + fontSize = 16.sp, + modifier = Modifier + .fillMaxWidth(0.4f) + ) + + } + + AsyncImage( + model = "https://www.thepowerof10.info/$picture", + modifier = Modifier + .heightIn(max = 250.dp) + .fillMaxWidth(), + contentDescription = "profile picture", + contentScale = ContentScale.FillWidth + ) + } + Button(onClick = { + val link = Uri.parse(athlete).toString() + val encodedUrl = URLEncoder.encode(link, StandardCharsets.UTF_8.toString())//cant pass url as params without encoding first + navController.navigate("Chart/$encodedUrl") }) { + Text(text = "Analyse!") + + } + + + Jsoup.parse(coaches).getElementsByTag("tr").forEach {//displays all the coaches + Row(Modifier.padding(horizontal = 5.dp)) { + for (i in it.children()) { //puts them on separate lines + Text( + text = i.text(), + fontSize = 14.sp, + modifier = Modifier + .padding(horizontal = 5.dp) + .fillParentMaxWidth(0.25f) + ) + } + } + + } + + + Text( + text = "About", + fontSize = 16.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.padding(horizontal = 10.dp) + ) + HtmlText( //formatted straight from html using htmlcompose library + text = about, + fontSize = 16.sp, + modifier = Modifier.padding( + top = 5.dp, + start = 10.dp, + end = 10.dp, + bottom = 20.dp + ) + ) + + + } + item { + + PerformanceSummary(//the expand collapse performances + bestPerformancesSize, + bestPerformances, + { viewModel.onItemClicked(99) }, + itemIds.contains(99) + ) + } + + items(allPerformances.size) { + Performances( + index = it, + onClickItem = { + viewModel.onItemClicked(it) + }, + expanded = itemIds.contains(it), + performances = allPerformances + ) + } + }) + + + } + }) + + } + } +} + +@Preview(name = "PIXEL_45", ) +@Composable +fun DefaultPreview5() { + + val navController = rememberNavController() + PowerOf10Theme { + //AthleteProfile(navController)//, viewModel) + //ExpandableView(performances = Elements(), isExpanded = true) + } +} + +@Composable +fun Performances(index: Int=0, onClickItem: () -> Unit, expanded: Boolean,performances: Elements){ + Column(modifier = Modifier.padding()) { + HeaderView(index,year = performances.eq(index).first()!!.child(0).child(0).text(), onClickItem = onClickItem) + ExpandableView( + index=index, + performances =performances , + isExpanded = expanded + ) + } + + +} + + + +@Composable +fun HeaderView(index: Int=1,year: String, onClickItem: () -> Unit) { + val background: Color = if (index%2==1){ + Color(0xFFE5383B) + } + else { + Color(0xFF7E7C7C) + } //alternates the background colour + Box(contentAlignment = Alignment.Center, + modifier = Modifier + .background(background) + .clickable( // Removes the ripple effect on tap + onClick = onClickItem + ) + .padding(20.dp) + .fillMaxWidth() + ) { + Text( + text = year, + fontSize = 23.sp, + fontWeight = FontWeight.Bold, + color = Color.White, + modifier = Modifier + ) + Icon(imageVector = Icons.Filled.ArrowDropDown, tint = Color.White, contentDescription = "Dropdown", + modifier = Modifier + .size(43.dp) + .align(Alignment.CenterEnd) + ) + } +} +@Composable +fun ExpandableView(index:Int,performances: Elements, isExpanded: Boolean) { + + // Opening Animation + val expandTransition = remember { + expandVertically( + expandFrom = Alignment.Top, + animationSpec = tween(300) + ) + fadeIn( + animationSpec = tween(300) + ) + } + + // Closing Animation + val collapseTransition = remember { + shrinkVertically( + shrinkTowards = Alignment.Top, + animationSpec = tween(300) + ) + fadeOut( + animationSpec = tween(300) + ) + } + + AnimatedVisibility( + visible = isExpanded, + enter = expandTransition, + exit = collapseTransition + ) { + Column() { + Performance(index=index,performances = performances) + } + } +} +@Composable +fun Performance(index:Int,performances: Elements){ + for (i in 1 until performances.eq(index).first()!!.getElementsByTag("tr").size) { + val background = if (i % 2 == 1) {//alternates the colour + Color(0xFFD9D9D9) + + } else { + Color(0xFFFFFFFF) + + } + + Row( + Modifier + .fillMaxWidth() + .background(background) + .padding(5.dp)) { + Column(Modifier.fillMaxWidth(0.15f)) { + Text(text =performances.eq(index).first()!!.child(i).child(0).text())//event + Text(text = performances.eq(index).first()!!.child(i).child(1).text())//performance + + } + Column(Modifier.fillMaxWidth(0.12f)) { + Text(text = performances.eq(index).first()!!.child(i).child(2).text())//windy or indoor? + Text(text = performances.eq(index).first()!!.child(i).child(3).text())//wind reading + } + Column(Modifier.fillMaxWidth(0.55f)) { + Text(text = performances.eq(index).first()!!.child(i).child(10).text())//meet name + } + Column(Modifier.fillMaxWidth(0.2f)) { + Text(text = performances.eq(index).first()!!.child(i).child(5).text())//position + Text(text = performances.eq(index).first()!!.child(i).child(6).text())//race stage + } + Column(Modifier.fillMaxWidth()) { + Text(text = performances.eq(index).first()!!.child(i).child(9).text())//meet location + Text(text = performances.eq(index).first()!!.child(i).child(11).text())//date + } + } + } +} + +@Composable +fun PerformanceGrid(performanceSize:Int,performances: Elements){ + for (index in 0 until performanceSize) { + LazyRow(content = { + item { + Box( + modifier = Modifier + .size(size = 100.dp) + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color(0xFFE11115), + Color(0xFF9E0D10) + ) + ) + ), contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = performances.eq(index + 1).first()?.child(0)?.text().toString(), + fontWeight = FontWeight.Bold, fontSize = 22.sp, color = Color.White + ) + Text( + text = "PB:" + performances.eq(index + 1).first()?.child(1)?.text() + .toString(), + color = Color.White, fontSize = 13.sp + ) + } + + } + } + items(10) {//number of years to display + val background = if (it % 2 == 1) {//alternates the colour + listOf( + Color(0xFFD9D9D9), + Color(0xFFACACAC) + ) + } else { + listOf( + Color(0xFF7A7979), + Color(0xFF626161) + ) + } //alternates the background colour + Box( + modifier = Modifier + .size(size = 100.dp) + .background( + brush = Brush.verticalGradient( + colors = background + ) + ), contentAlignment = Alignment.Center + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = performances.first()?.child(it + 2)?.text().toString(), + fontWeight = FontWeight.Bold, fontSize = 22.sp, color = Color.Black + ) + Text( + text = "SB:" + performances.eq(index + 1).first()?.child(it + 2)?.text() + .toString(), + color = Color.Black, fontSize = 13.sp + ) + } + } + + } + }) + } +} +@Composable +fun PerformanceExpandableView( performanceSize:Int,performances: Elements,isExpanded: Boolean) { + // Opening Animation + val expandTransition = remember { + expandVertically( + expandFrom = Alignment.Top, + animationSpec = tween(300) + ) + fadeIn( + animationSpec = tween(300) + ) + } + + // Closing Animation + val collapseTransition = remember { + shrinkVertically( + shrinkTowards = Alignment.Top, + animationSpec = tween(300) + ) + fadeOut( + animationSpec = tween(300) + ) + } + + AnimatedVisibility( + visible = isExpanded, + enter = expandTransition, + exit = collapseTransition + ) { + Column(){ + PerformanceGrid(performanceSize = performanceSize, performances =performances ) + } + } +} +@Composable +fun PerformanceSummary(performanceSize:Int,performances: Elements,onClickItem: () -> Unit, expanded: Boolean){ + Column(modifier = Modifier.padding()) { + HeaderView(year = "Best Performances", onClickItem = onClickItem) + + PerformanceExpandableView(performanceSize = performanceSize, performances = performances, isExpanded = expanded) + } +} diff --git a/Dissertation/app/src/main/java/com/example/powerof10/AthleteSearch.kt b/Dissertation/app/src/main/java/com/example/powerof10/AthleteSearch.kt new file mode 100644 index 0000000..09db78c --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/AthleteSearch.kt @@ -0,0 +1,331 @@ +package com.example.powerof10 + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.WindowInsetsAnimationController +import android.view.WindowInsetsController +import android.widget.Toast +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.* +import androidx.compose.foundation.gestures.scrollable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import com.example.powerof10.Components.NavDrawerBody +import com.example.powerof10.ui.theme.PowerOf10Theme +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.select.Elements +import tech.devscast.validable.EmailValidable +import tech.devscast.validable.delegates.validableEmail +import tech.devscast.validable.withValidable +import java.io.Serializable +import java.net.URLEncoder +import java.nio.charset.StandardCharsets + +class AthleteSearch : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + PowerOf10Theme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + + } + } + } + } +} + +suspend fun getHTML(firstName: String, lastName: String, club: String): Document { //retrieves the entire HTML page including tags using search parameters + val html= "https://www.thepowerof10.info/athletes/athleteslookup.aspx?surname=$lastName&firstname=$firstName&club=$club" + + return withContext(Dispatchers.IO) { + return@withContext Jsoup.connect(html).get() + } + +} + +fun getAthletes(doc: Document,mContext: Context,navController: NavController): Serializable? {//serializable = any data structure. Function converts html to a list of elements + if (doc.getElementsByAttributeValueContaining("class","athleteprofilesubheader").isNotEmpty()){ //Checks if the link returned the athlete profile due to there being only 1 result +// val browserIntent = Intent(//opens the browser and takes user to the link +// Intent.ACTION_VIEW, +// Uri.parse( +// "https://www.thepowerof10.info/athletes/" + doc.getElementById("form1")?.attr("action") +// ) +// ) +// browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)// Starts browser intent to athlete profile +// mContext.startActivity(browserIntent) + val link = Uri.parse("https://www.thepowerof10.info/athletes/" + doc.getElementById("form1")?.attr("action")).toString() + val encodedUrl = URLEncoder.encode(link, StandardCharsets.UTF_8.toString())//cant pass url as params without encoding first + navController.navigate("Profile/$encodedUrl")//navigates to profile instead of browser + + return "athlete found" + }else if(doc.getElementsContainingText("No athletes match criteria").isNotEmpty()){ + return "no athletes found" + }else if ((doc.getElementsContainingText("Too many athletes found. Please change the search criteria.").isNotEmpty())){ //theres too many athlete records that were returned + return "too many athletes found" + } + else{//if theres NOT too many records + val table = doc.getElementById("cphBody_dgAthletes") // returns the table of athlete search results (NEEDED as the nav bar has the background colour attribute) + var records = table?.getElementsByAttributeValueContaining("style","background-color:") //returns all records with a background colour within the results table(Line before) + records?.removeAt(0)//removes the heading list(sex,name,etc) + return records + } + } + + + + + + + + +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AthleteSearch(navController: NavController) { + val powerOfTen = painterResource(id = R.drawable.powerof10) + var buttonPress by remember { mutableStateOf(0) }//updates the lazy column when the apply button is pressed + val scaffoldState = rememberScaffoldState() + val scope = rememberCoroutineScope() + var firstName by remember { mutableStateOf("") } + var lastName by remember { mutableStateOf("") } + var club by remember { mutableStateOf("") } + var athletesList by remember {mutableStateOf(Elements()) } + var context = LocalContext.current + LaunchedEffect(buttonPress) { //recomposes on button press + if (firstName != ""||lastName != ""){//if the either name fields are not empty + val html = getHTML(firstName,lastName,club)//gets html from the searched page + var athletesElements = getAthletes(html, context,navController)//this is to check if the result is string, null or Elements() + when (athletesElements) { + "too many athletes found" -> {//if null make a warning/toast + Toast.makeText(context,"Too many athletes found. Please change the search criteria.\n",Toast.LENGTH_SHORT).show() + } + "athlete found" -> {//if only a single athlete is returned + Toast.makeText(context,"Athlete found opening on browser",Toast.LENGTH_SHORT).show() + + } + "no athletes found" -> { + Toast.makeText(context,"No athlete was found",Toast.LENGTH_SHORT).show() + } + else -> {//otherwise just display the records list as usual + athletesList = athletesElements as Elements + + } + } + + + }else{// if both fields are empty + Toast.makeText(context,"Please enter your search values",Toast.LENGTH_SHORT).show() + } + + + + + } + Column { + + Scaffold( + scaffoldState = scaffoldState, + topBar = { + CenterAlignedTopAppBar(modifier = Modifier.fillMaxWidth(), + title = { + Image( + painter = powerOfTen, + contentDescription = "Power of Ten Logo", + modifier = Modifier.padding(5.dp), + contentScale = ContentScale.FillBounds + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + Color(0xFFE5383B), + navigationIconContentColor = Color.White + ), + navigationIcon = { + IconButton(onClick = { scope.launch { scaffoldState.drawerState.open() } }) { + Icon( + Icons.Filled.Menu, contentDescription = null, + tint = Color.White, + modifier = Modifier.size(48.dp) + ) + } + } + ) + }, + drawerContent = { + NavDrawerBody(items = NavigationOptions.nav, onItemClick = { navController.navigate(route =it.route) } + ) + }, content = { + + + Column( modifier = Modifier.fillMaxWidth() + ){ + + + LazyColumn(content = { + item { + Column(horizontalAlignment = Alignment.CenterHorizontally,modifier = Modifier.fillMaxWidth() + ) { + Text( + text = "Athlete Search", + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + modifier = Modifier.padding(10.dp) + ) + TextField( + value = firstName, + onValueChange = { firstName = it }, + label = { Text("First Name") }, + modifier = Modifier.padding(0.dp,5.dp) + ) + + TextField( + value = lastName, + onValueChange = { lastName = it }, + label = { Text("Last Name") }, + modifier = Modifier.padding(0.dp,5.dp) + ) + + TextField( + value = club, + onValueChange = { club = it }, + label = { Text("Club Name") }, + modifier = Modifier.padding(0.dp,5.dp) + ) + Button( + onClick = { + if (firstName.length < 3&&lastName.length<3 ){// validates to check fields are atleast 3 chars long + Toast.makeText(context,"Please enter at least 3 characters for either First or Last name",Toast.LENGTH_SHORT).show() + }else { + buttonPress += 1 //recompose view + } + }, modifier = Modifier + .padding(horizontal = 5.dp, vertical = 0.dp) + .fillMaxWidth(0.75F) + + ) { + Text("Apply") + + } + } + } + items(athletesList.size) { + + AthleteItem(it,athletesList,navController) + + + } + }) + + } + }) + + } +} + +@Composable +fun AthleteItem(index: Int,athlete:Elements,navController: NavController) { + val mContext = LocalContext.current + val background: Color = if (index%2==1){//alternates the colour + Color(0xFFD3D3D3) + } + else { + Color(0xFFF5F3F4) + } //alternates the background colour + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier + .background(background) + .fillMaxWidth() + .padding(0.dp, 10.dp) + .clickable { + val link = Uri.parse(athlete[index].child(7).child(0).attr("href")).toString() + val encodedUrl = URLEncoder.encode(link, StandardCharsets.UTF_8.toString())//cant pass url as params without encoding first + navController.navigate("Profile/$encodedUrl")//navigates to profile instead of browser +// val browserIntent = Intent( +// Intent.ACTION_VIEW, +// Uri.parse( +// "https://www.thepowerof10.info/athletes/" + athlete[index] +// .child(7) +// .child(0) +// .attr("href") +// ) +// ) +// browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)// Starts browser intent to athlete profile +// mContext.startActivity(browserIntent) + } + ) { + + + Column(verticalArrangement = Arrangement.SpaceBetween,modifier = Modifier + .fillMaxWidth(0.25f) + .padding(start = 5.dp)) { + + Text(text = athlete[index].child(0).text() ,//First Name + maxLines = 1, + overflow = TextOverflow.Ellipsis) + Text(text = athlete[index].child(1).text() ,//Last Name + maxLines = 1, + overflow = TextOverflow.Ellipsis) + } + + Column(verticalArrangement = Arrangement.SpaceBetween,modifier = Modifier.fillMaxWidth(0.5f)) { + Text(text = "Club:" ,//Athlete's club + maxLines = 1, + overflow = TextOverflow.Ellipsis) + Text(text = athlete[index].child(6).text() ,//athletes club + maxLines = 1, + overflow = TextOverflow.Ellipsis) + + } + Column(modifier = Modifier.fillMaxWidth(0.4f)) { + Text(text = athlete[index].child(5).text() ,//Athlete's Gender + maxLines = 1, + overflow = TextOverflow.Ellipsis) + Text(text = athlete[index].child(2).text() ,//Athletes Age Group + maxLines = 1, + overflow = TextOverflow.Ellipsis) + + + } + } +} +@Preview(name = "PIXEL_4", device = Devices.PIXEL_4) +//@Preview(name = "PIXEL_42", device = Devices.PIXEL_4) +@Composable +fun DefaultPreview3() { + PowerOf10Theme { + AthleteSearch(rememberNavController()) + } +} \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/ChartStyle.kt b/Dissertation/app/src/main/java/com/example/powerof10/ChartStyle.kt new file mode 100644 index 0000000..7a5055d --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/ChartStyle.kt @@ -0,0 +1,62 @@ +package com.patrykandpatrick.vico.sample.showcase + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import com.patrykandpatrick.vico.compose.component.shape.shader.fromBrush +import com.patrykandpatrick.vico.compose.style.ChartStyle +import com.patrykandpatrick.vico.core.DefaultAlpha +import com.patrykandpatrick.vico.core.DefaultColors +import com.patrykandpatrick.vico.core.DefaultDimens +import com.patrykandpatrick.vico.core.chart.line.LineChart +import com.patrykandpatrick.vico.core.component.shape.LineComponent +import com.patrykandpatrick.vico.core.component.shape.Shapes +import com.patrykandpatrick.vico.core.component.shape.shader.DynamicShaders + +@Composable +internal fun rememberChartStyle(columnChartColors: List, lineChartColors: List): ChartStyle { + val isSystemInDarkTheme = isSystemInDarkTheme() + return remember(columnChartColors, lineChartColors, isSystemInDarkTheme) { + val defaultColors = if (isSystemInDarkTheme) DefaultColors.Dark else DefaultColors.Light + ChartStyle( + ChartStyle.Axis( + axisLabelColor = Color(defaultColors.axisLabelColor), + axisGuidelineColor = Color(defaultColors.axisGuidelineColor), + axisLineColor = Color(defaultColors.axisLineColor), + ), + ChartStyle.ColumnChart( + columnChartColors.map { columnChartColor -> + LineComponent( + columnChartColor.toArgb(), + DefaultDimens.COLUMN_WIDTH, + Shapes.roundedCornerShape(DefaultDimens.COLUMN_ROUNDNESS_PERCENT), + ) + }, + ), + ChartStyle.LineChart( + lineChartColors.map { lineChartColor -> + LineChart.LineSpec( + lineColor = lineChartColor.toArgb(), + lineBackgroundShader = DynamicShaders.fromBrush( + Brush.verticalGradient( + listOf( + lineChartColor.copy(DefaultAlpha.LINE_BACKGROUND_SHADER_START), + lineChartColor.copy(DefaultAlpha.LINE_BACKGROUND_SHADER_END), + ), + ), + ), + ) + }, + ), + ChartStyle.Marker(), + Color(defaultColors.elevationOverlayColor), + ) + } +} + +@Composable +internal fun rememberChartStyle(chartColors: List) = + rememberChartStyle(columnChartColors = chartColors, lineChartColors = chartColors) \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/ChartsPage.kt b/Dissertation/app/src/main/java/com/example/powerof10/ChartsPage.kt new file mode 100644 index 0000000..4ce9bba --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/ChartsPage.kt @@ -0,0 +1,161 @@ +package com.example.powerof10 + +//import androidx.compose.ui.platform.LocalContext +import android.annotation.SuppressLint +import androidx.compose.animation.* +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import com.example.powerof10.Components.NavDrawerBody +import com.example.powerof10.ui.theme.PowerOf10Theme +import com.patrykandpatrick.vico.core.entry.FloatEntry +import kotlinx.coroutines.launch +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.time.Month + + +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ChartsPage(navController: NavController,athlete: String) { + val powerOfTen = painterResource(id = R.drawable.powerof10) + val scaffoldState = rememberScaffoldState() + var isRSSLoading by remember { mutableStateOf(true) } //the state of whether the rss data has loaded yet + var ldata by remember { mutableStateOf(mutableMapOf("60" to mutableListOf())) } + val scope = rememberCoroutineScope() + var chart2data by remember { mutableStateOf(mutableListOf()) } + var chart3data by remember { mutableStateOf(mutableMapOf>()) } + var chart4data by remember { mutableStateOf(mutableMapOf>()) } + var chart5data by remember { mutableStateOf(mutableMapOf>()) } + var chart6data by remember { mutableStateOf(mutableMapOf()) } + var html:Document = Document("") + LaunchedEffect(Unit) { + html = getHTML(athlete) + println(athlete) + chart2data = Overall60Season(html) + chart3data = timeByVenue(html) + chart4data = PbsByVenue() + chart5data = FastestByMonth() + chart6data = PBsByMonth() + isRSSLoading = false + ldata = AthleteDataCollection().lifetimePerformances(html)//lifetimedata + + } + + //var context = LocalContext.current + if (isRSSLoading) {// if the RSS hasn't loaded circular progress indicator will appear + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxSize() + ) { + CircularProgressIndicator() + } + } + if (!isRSSLoading) {// hides everything till loaded + Column { + + Scaffold( + scaffoldState = scaffoldState, + topBar = { + CenterAlignedTopAppBar(modifier = Modifier.fillMaxWidth(), + title = { + Image( + painter = powerOfTen, + contentDescription = "Power of Ten Logo", + modifier = Modifier.padding(5.dp), + contentScale = ContentScale.FillBounds + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + Color(0xFFE5383B), + navigationIconContentColor = Color.White + ), + navigationIcon = { + IconButton(onClick = { scope.launch { scaffoldState.drawerState.open() } }) { + Icon( + Icons.Filled.Menu, contentDescription = null, + tint = Color.White, + modifier = Modifier.size(48.dp) + ) + } + } + ) + }, + drawerContent = { + NavDrawerBody( + items = NavigationOptions.nav, + onItemClick = { navController.navigate(route = it.route) } + ) + }, + content = { + LazyColumn(modifier =Modifier.fillMaxSize() , content = { + item{ + Text(text = "Athlete Analysis", fontSize = 30.sp, fontWeight = FontWeight.Bold) + Text(text = "Performance vs Country by year") + Text(text = "You", color = Color(0xffb983ff),fontWeight = FontWeight.Bold) + Text(text = "Best Performance in the country",color = Color(0xff91b1fd),fontWeight = FontWeight.Bold) + BestVSCountryChart(ldata) + Text(text = "Performance over season") + BestOverSeasonChart(chart2data) + Text(text = "Best Performance by Venue") + BestByVenueChart(chart3data) + Text(text = "Overall Men") + Text(text = "Best Performance by Venue") + VenueByTimesChart(chart4data) + Text(text = "Overall Men") + Text(text = "Best Performance by Month") + FastestTimeByMonthChart(chart5data) + Text(text = "Overall Men") + Text(text = "PBs by Month") + PBsByMonthChart(chart6data) + } + + + }) + } + ) + + + + + + + + + + } + + } +} + + +@Preview(name = "PIXEL_45") +@Composable +fun DefaultPreview8() { + + val navController = rememberNavController() + PowerOf10Theme { + //AthleteProfile(navController)//, viewModel) + //ExpandableView(performances = Elements(), isExpanded = true) + } +} + diff --git a/Dissertation/app/src/main/java/com/example/powerof10/Common/HTTPDataHandler.java b/Dissertation/app/src/main/java/com/example/powerof10/Common/HTTPDataHandler.java new file mode 100644 index 0000000..e7efb89 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/Common/HTTPDataHandler.java @@ -0,0 +1,43 @@ +package com.example.powerof10.Common; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; + +public class HTTPDataHandler { + static String stream=""; + + public HTTPDataHandler () {} + + public String GetHTTPDataHandler(String urlString) + { + try + { + URL url = new URL(urlString); + HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection(); + + if(urlConnection.getResponseCode()== HttpURLConnection.HTTP_OK) + { + InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream()); + + BufferedReader r = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder sb = new StringBuilder(); + String line =""; + while ((line = r.readLine()) != null) + sb.append(line); + stream = sb.toString(); + urlConnection.disconnect(); + + + } + } catch (Exception ex) { + return null; + } + return stream; + } +} diff --git a/Dissertation/app/src/main/java/com/example/powerof10/Components/Legend.kt b/Dissertation/app/src/main/java/com/example/powerof10/Components/Legend.kt new file mode 100644 index 0000000..832e922 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/Components/Legend.kt @@ -0,0 +1,33 @@ +package com.example.powerof10.Components + +import android.graphics.Typeface +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.patrykandpatrick.vico.compose.component.shapeComponent +import com.patrykandpatrick.vico.compose.component.textComponent +import com.patrykandpatrick.vico.compose.dimensions.dimensionsOf +import com.patrykandpatrick.vico.compose.legend.verticalLegend +import com.patrykandpatrick.vico.compose.legend.verticalLegendItem +import com.patrykandpatrick.vico.compose.style.currentChartStyle +import com.patrykandpatrick.vico.core.component.shape.Shapes + +var chartColors = listOf(Color.Red,Color.Yellow) +@Composable +fun rememberLegend() = verticalLegend( + items = chartColors.mapIndexed { index, chartColor -> + verticalLegendItem( + icon = shapeComponent(Shapes.pillShape, chartColor), + label = textComponent( + color = currentChartStyle.axis.axisLabelColor, + textSize = 16.sp, + typeface = Typeface.MONOSPACE, + ), + labelText = " Dataset $index", + ) + }, + iconSize = 16.dp, + iconPadding = 0.dp, + padding = dimensionsOf(top = 0.dp), +) \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/Components/Marker.kt b/Dissertation/app/src/main/java/com/example/powerof10/Components/Marker.kt new file mode 100644 index 0000000..347102d --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/Components/Marker.kt @@ -0,0 +1,118 @@ + +/* + * Copyright 2023 by Patryk Goworowski and Patrick Michalik. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.patrykandpatrick.vico.sample.showcase + +import android.graphics.Typeface +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.unit.dp +import com.patrykandpatrick.vico.compose.component.lineComponent +import com.patrykandpatrick.vico.compose.component.overlayingComponent +import com.patrykandpatrick.vico.compose.component.shapeComponent +import com.patrykandpatrick.vico.compose.component.textComponent +import com.patrykandpatrick.vico.compose.dimensions.dimensionsOf +import com.patrykandpatrick.vico.core.chart.insets.Insets +import com.patrykandpatrick.vico.core.chart.segment.SegmentProperties +import com.patrykandpatrick.vico.core.component.marker.MarkerComponent +import com.patrykandpatrick.vico.core.component.shape.DashedShape +import com.patrykandpatrick.vico.core.component.shape.ShapeComponent +import com.patrykandpatrick.vico.core.component.shape.Shapes +import com.patrykandpatrick.vico.core.component.shape.cornered.Corner +import com.patrykandpatrick.vico.core.component.shape.cornered.MarkerCorneredShape +import com.patrykandpatrick.vico.core.context.MeasureContext +import com.patrykandpatrick.vico.core.extension.copyColor +import com.patrykandpatrick.vico.core.marker.Marker + +@Composable +internal fun rememberMarker(): Marker { + val labelBackgroundColor = MaterialTheme.colorScheme.surface + val labelBackground = remember(labelBackgroundColor) { + ShapeComponent(labelBackgroundShape, labelBackgroundColor.toArgb()).setShadow( + radius = LABEL_BACKGROUND_SHADOW_RADIUS, + dy = LABEL_BACKGROUND_SHADOW_DY, + applyElevationOverlay = true, + ) + } + val label = textComponent( + background = labelBackground, + lineCount = LABEL_LINE_COUNT, + padding = labelPadding, + typeface = Typeface.MONOSPACE, + ) + val indicatorInnerComponent = shapeComponent(Shapes.pillShape, MaterialTheme.colorScheme.surface) + val indicatorCenterComponent = shapeComponent(Shapes.pillShape, Color.White) + val indicatorOuterComponent = shapeComponent(Shapes.pillShape, Color.White) + val indicator = overlayingComponent( + outer = indicatorOuterComponent, + inner = overlayingComponent( + outer = indicatorCenterComponent, + inner = indicatorInnerComponent, + innerPaddingAll = indicatorInnerAndCenterComponentPaddingValue, + ), + innerPaddingAll = indicatorCenterAndOuterComponentPaddingValue, + ) + val guideline = lineComponent( + MaterialTheme.colorScheme.onSurface.copy(GUIDELINE_ALPHA), + guidelineThickness, + guidelineShape, + ) + return remember(label, indicator, guideline) { + object : MarkerComponent(label, indicator, guideline) { + init { + indicatorSizeDp = INDICATOR_SIZE_DP + onApplyEntryColor = { entryColor -> + indicatorOuterComponent.color = entryColor.copyColor(INDICATOR_OUTER_COMPONENT_ALPHA) + with(indicatorCenterComponent) { + color = entryColor + setShadow(radius = INDICATOR_CENTER_COMPONENT_SHADOW_RADIUS, color = entryColor) + } + } + } + + override fun getInsets(context: MeasureContext, outInsets: Insets, segmentProperties: SegmentProperties) = + with(context) { + outInsets.top = label.getHeight(context) + labelBackgroundShape.tickSizeDp.pixels + + LABEL_BACKGROUND_SHADOW_RADIUS.pixels * SHADOW_RADIUS_MULTIPLIER - + LABEL_BACKGROUND_SHADOW_DY.pixels + } + } + } +} + +private const val LABEL_BACKGROUND_SHADOW_RADIUS = 4f +private const val LABEL_BACKGROUND_SHADOW_DY = 2f +private const val LABEL_LINE_COUNT = 1 +private const val GUIDELINE_ALPHA = .2f +private const val INDICATOR_SIZE_DP = 36f +private const val INDICATOR_OUTER_COMPONENT_ALPHA = 32 +private const val INDICATOR_CENTER_COMPONENT_SHADOW_RADIUS = 12f +private const val GUIDELINE_DASH_LENGTH_DP = 8f +private const val GUIDELINE_GAP_LENGTH_DP = 4f +private const val SHADOW_RADIUS_MULTIPLIER = 1.3f + +private val labelBackgroundShape = MarkerCorneredShape(Corner.FullyRounded) +private val labelHorizontalPaddingValue = 8.dp +private val labelVerticalPaddingValue = 4.dp +private val labelPadding = dimensionsOf(labelHorizontalPaddingValue, labelVerticalPaddingValue) +private val indicatorInnerAndCenterComponentPaddingValue = 5.dp +private val indicatorCenterAndOuterComponentPaddingValue = 10.dp +private val guidelineThickness = 2.dp +private val guidelineShape = DashedShape(Shapes.pillShape, GUIDELINE_DASH_LENGTH_DP, GUIDELINE_GAP_LENGTH_DP) diff --git a/Dissertation/app/src/main/java/com/example/powerof10/Components/NavDrawer.kt b/Dissertation/app/src/main/java/com/example/powerof10/Components/NavDrawer.kt new file mode 100644 index 0000000..d8f39b1 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/Components/NavDrawer.kt @@ -0,0 +1,62 @@ +package com.example.powerof10.Components + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.example.powerof10.MenuItems +import com.example.powerof10.ui.theme.PowerOf10Theme + +class NavDrawer : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + PowerOf10Theme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + + } + } + } + } +} + + +@Composable +fun NavDrawerBody(items:List, onItemClick: (MenuItems)->Unit) { + + LazyColumn(modifier = Modifier.fillMaxSize().background(Color(0xFFE5383B))){ + items(items){item -> + Row(modifier = Modifier + .clickable { onItemClick(item) } + .padding(20.dp)) { + Text(text = item.title, fontSize = 24.sp, color = Color.White) + Spacer(modifier = Modifier.fillMaxWidth()) + } + } + } +} + + +@Composable +fun DefaultPreview2() { + PowerOf10Theme { + + } +} \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/ExpandableListViewModel.kt b/Dissertation/app/src/main/java/com/example/powerof10/ExpandableListViewModel.kt new file mode 100644 index 0000000..9d98389 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/ExpandableListViewModel.kt @@ -0,0 +1,67 @@ +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class ExpandableListViewModel : ViewModel() { + + private val itemsList = MutableStateFlow(listOf()) + val items: StateFlow> get() = itemsList + private val itemIdsList = MutableStateFlow(listOf()) + val itemIds: StateFlow> get() = itemIdsList + + init { + getData() + } + fun onItemClicked(itemId: Int) { + itemIdsList.value = itemIdsList.value.toMutableList().also { list -> + if (list.contains(itemId)) { + list.remove(itemId) + } else { + list.add(itemId) + } + } + } + + private fun getData() { + viewModelScope.launch { + withContext(Dispatchers.Default) { + itemsList.emit(Data.items) + } + } + } + +} +data class DataModel(val question: String, val answer: String) +object Data { + var items = arrayListOf( + DataModel( + "Item 0", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + ), + DataModel( + "Item 1", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam" + ), + DataModel( + "Item 2", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut" + ), + DataModel( + "Item 3", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + ), + DataModel( + "Item 4", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + ), + DataModel( + "Item 5", + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum." + ), + // ... + ) +} diff --git a/Dissertation/app/src/main/java/com/example/powerof10/HomeRSS.kt b/Dissertation/app/src/main/java/com/example/powerof10/HomeRSS.kt new file mode 100644 index 0000000..542a47b --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/HomeRSS.kt @@ -0,0 +1,275 @@ +package com.example.powerof10 + +import android.annotation.SuppressLint +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.ViewModel +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import coil.compose.AsyncImage +import com.example.powerof10.Components.NavDrawerBody +import com.example.powerof10.ui.theme.PowerOf10Theme +import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers.IO +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import tw.ktrssreader.Reader +import tw.ktrssreader.kotlin.model.channel.RssStandardChannelData +import java.net.URLEncoder +import java.nio.charset.StandardCharsets + + +class HomeRSS : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + PowerOf10Theme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + + } + } + } + } +} + +class RSSLogic { + private val rSSLink = "https://athleticsweekly.com/feed/" + // val RSS_toJsonAPI = "https://api.rss2json.com/v1/api.json?rss_url=" + + //Returns and parses the RSS feed using the KTRSSReader library + //Does this using the IO dispatcher thread + suspend fun assignResult(): RssStandardChannelData { + return withContext(IO) { + return@withContext Reader.coRead(rSSLink) + } + + } + + +} + +//Extracts the description in the "

" tag from the RSS " tag" +fun extractP(description: String?): String { + val doc: Document = Jsoup.parse(description) + val p: Element = doc.select("p").first()!! + return p.text() +} + +//Extracts the image source src attribute within the "" tag from the RSS " tag" +fun extractImg(description: String?): String { + val doc: Document = Jsoup.parse(description) + val img = doc.select("img").first() + return img!!.attr("src") + +} +// +//@Preview(name = "NEXUS_7_2013", device = Devices.NEXUS_7_2013) +//@Preview(name = "NEXUS_5", device = Devices.NEXUS_5) +//@Preview(name = "NEXUS_6", device = Devices.NEXUS_6) +//@Preview(name = "NEXUS_9", device = Devices.NEXUS_9) +//@Preview(name = "NEXUS_10", device = Devices.NEXUS_10) +//@Preview(name = "NEXUS_5X", device = Devices.NEXUS_5X) +//@Preview(name = "NEXUS_6P", device = Devices.NEXUS_6P) +//@Preview(name = "PIXEL_C", device = Devices.PIXEL_C) +//@Preview(name = "PIXEL", device = Devices.PIXEL) +//@Preview(name = "PIXEL_XL", device = Devices.PIXEL_XL) +//@Preview(name = "PIXEL_2", device = Devices.PIXEL_2) +//@Preview(name = "PIXEL_2_XL", device = Devices.PIXEL_2_XL) +//@Preview(name = "PIXEL_3", device = Devices.PIXEL_3) +//@Preview(name = "PIXEL_3_XL", device = Devices.PIXEL_3_XL) +//@Preview(name = "PIXEL_3A", device = Devices.PIXEL_3A) +//@Preview(name = "PIXEL_3A_XL", device = Devices.PIXEL_3A_XL) +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@OptIn(ExperimentalMaterial3Api::class) +//@Preview(name = "PIXEL_4", device = Devices.PIXEL_4) +//@Preview(name = "PIXEL_4_XL", device = Devices.PIXEL_4_XL) +@Composable +fun DisplayRssHomepage(navController: NavController) { + var rssitem by remember {// If any changes are made to this variable the screen recomposes also stores the RSS Data + mutableStateOf( + RssStandardChannelData( + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ) + ) + } + var isRSSLoading by remember { mutableStateOf(true) } //the state of whether the rss data has loaded yet + Log.d("test", "composed") + + LaunchedEffect(Unit) { + rssitem = RSSLogic().assignResult() //returns rss data as RssDataStandardData Object + isRSSLoading = false //disables loading circle + + } + val scaffoldState = rememberScaffoldState() + val scope = rememberCoroutineScope() + val powerOfTen = painterResource(id = R.drawable.powerof10) + + + + Column { + Scaffold( + scaffoldState = scaffoldState, + topBar = { + CenterAlignedTopAppBar(modifier = Modifier.fillMaxWidth(), + title = { + Image( + painter = powerOfTen, + contentDescription = "Power of Ten Logo", + modifier = Modifier.padding(5.dp), + contentScale = ContentScale.FillBounds + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + Color(0xFFE5383B), + navigationIconContentColor = Color.White + ), + navigationIcon = { + IconButton(onClick = { scope.launch { scaffoldState.drawerState.open() } }) { + Icon( + Icons.Filled.Menu, contentDescription = null, + tint = Color.White, + modifier = Modifier.size(48.dp) + ) + } + } + ) + }, + drawerContent = { + NavDrawerBody(items = NavigationOptions.nav, onItemClick = { navController.navigate(route =it.route) } + ) + }, content = { +Column() { + + + LazyColumn(content = { + item { Text( + text = "Latest News", + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + modifier = Modifier.padding(10.dp) + ) } + rssitem.items?.let { it -> + items(it.size) { + RssItem(it, rssitem = rssitem,navController) + } + } + }) +} + }) + + } + if (isRSSLoading) {// if the RSS hasn't loaded circular progress indicator will appear + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxSize() + ) { + CircularProgressIndicator() + } + } +} + + +// The RSS item for the Lazy Column Composable (Recycler View) +@Composable +fun RssItem(index: Int, rssitem: RssStandardChannelData,navController: NavController) { + val mContext = LocalContext.current + Column(modifier = Modifier.clickable {// On click a browser intent is started to the link of the article +// val browserIntent = +// Intent(Intent.ACTION_VIEW, Uri.parse(rssitem.items?.get(index)?.link)) +// browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) +// mContext.startActivity(browserIntent) + val link = Uri.parse(rssitem.items?.get(index)?.link).toString() + val encodedUrl = URLEncoder.encode(link, StandardCharsets.UTF_8.toString())//cant pass url as params without encoding first + navController.navigate("Article/$encodedUrl")//navigates to webview instead of browser + }) { + Box { + AsyncImage( //coil library composable that loads images from the the src url on another thread + model = extractImg(rssitem.items?.get(index)?.description), + contentDescription = "Rss Image", + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + Text(//Title + text = " " + (rssitem.items?.get(index)?.title), + color = Color.White, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontSize = 24.sp, + modifier = Modifier + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color.Transparent, + Color.Black + ) + ) + ) + .fillMaxWidth() + .align( + Alignment.BottomStart + ) + ) + } + + Text( //description + text = " " + extractP(rssitem.items?.get(index)?.description), + fontSize = 16.sp, + modifier = Modifier + ) + + + } + + +} diff --git a/Dissertation/app/src/main/java/com/example/powerof10/Interface/ItemClickListener.java b/Dissertation/app/src/main/java/com/example/powerof10/Interface/ItemClickListener.java new file mode 100644 index 0000000..8b10728 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/Interface/ItemClickListener.java @@ -0,0 +1,7 @@ +package com.example.powerof10.Interface; + +import android.view.View; + +public interface ItemClickListener { + void onClick(View view, int position, boolean isLongClick); +} diff --git a/Dissertation/app/src/main/java/com/example/powerof10/LineChartDemo.kt b/Dissertation/app/src/main/java/com/example/powerof10/LineChartDemo.kt new file mode 100644 index 0000000..95d7adf --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/LineChartDemo.kt @@ -0,0 +1,159 @@ +package com.himanshoe.charty.ui + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.MaterialTheme.colors +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.himanshoe.charty.common.axis.AxisConfig +import com.himanshoe.charty.line.LineChart +import com.himanshoe.charty.line.config.LineConfig +import com.himanshoe.charty.line.model.LineData + +@Composable +fun LineChartDemo() { + var colors = listOf(Color.Cyan,Color.Blue) + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(32.dp) + ) { + item { + LineChart( + modifier = Modifier + .fillMaxWidth() + .height(300.dp), + + colors = colors, + lineData = listOf( + LineData(10F, 35F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(20F, 25F), + LineData(10F, 50F), + LineData(80F, 10F), + LineData(10F, 15F), + LineData(50F, 100F), + LineData(20F, 25F), + ) + ) + } + item { + Text( + text = "Gradient Line Chart", + fontSize = 16.sp, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 32.dp), + textAlign = TextAlign.Center + ) + } + item { + LineChart( + modifier = Modifier + .fillMaxWidth() + .height(300.dp), + color = colors.first(), + lineData = listOf( + LineData(10F, 35F), + LineData(20F, 25F), + LineData(10F, 50F), + LineData(80F, 10F), + LineData(10F, 15F), + LineData(50F, 100F), + LineData(20F, 25F), + ) + ) + } + item { + Text( + text = "Solid Line Chart", + fontSize = 16.sp, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 32.dp), + textAlign = TextAlign.Center + ) + } + item { + LineChart( + modifier = Modifier + .fillMaxWidth() + .height(300.dp), + color = colors.first(), + lineConfig = LineConfig(hasDotMarker = false), + lineData = listOf( + LineData(10F, 35F), + LineData(20F, 25F), + LineData(10F, 50F), + LineData(80F, 10F), + LineData(10F, 15F), + LineData(50F, 100F), + LineData(20F, 25F), + ) + ) + } + item { + Text( + text = "Line Chart without marker", + fontSize = 16.sp, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 32.dp), + textAlign = TextAlign.Center + ) + } + item { + LineChart( + modifier = Modifier + .fillMaxWidth() + .height(300.dp), + color = colors.first(), + lineConfig = LineConfig(hasSmoothCurve = true), + lineData = listOf( + LineData(10F, 35F), + LineData(20F, 25F), + LineData(10F, 50F), + LineData(80F, 10F), + LineData(10F, 15F), + LineData(50F, 100F), + LineData(20F, 25F), + ) + ) + } + item { + Text( + text = "Line Chart with smooth curve", + fontSize = 16.sp, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 32.dp), + textAlign = TextAlign.Center + ) + } + } +} diff --git a/Dissertation/app/src/main/java/com/example/powerof10/MainActivity.kt b/Dissertation/app/src/main/java/com/example/powerof10/MainActivity.kt new file mode 100644 index 0000000..d4c9518 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/MainActivity.kt @@ -0,0 +1,34 @@ +package com.example.powerof10 + +import ExpandableListViewModel +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import com.example.powerof10.ui.theme.PowerOf10Theme + +class MainActivity : ComponentActivity() { + lateinit var navController: NavHostController + private val viewModel by viewModels() + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + PowerOf10Theme { + + navController = rememberNavController() + SetupNavGraph(navController = navController,viewModel) + } + } + } +} + + + + diff --git a/Dissertation/app/src/main/java/com/example/powerof10/MenuItems.kt b/Dissertation/app/src/main/java/com/example/powerof10/MenuItems.kt new file mode 100644 index 0000000..897949b --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/MenuItems.kt @@ -0,0 +1,7 @@ +package com.example.powerof10 + +data class MenuItems( + val id:Int, + val title: String, + val route:String +) diff --git a/Dissertation/app/src/main/java/com/example/powerof10/Model/Feed.kt b/Dissertation/app/src/main/java/com/example/powerof10/Model/Feed.kt new file mode 100644 index 0000000..c438df1 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/Model/Feed.kt @@ -0,0 +1,10 @@ +package com.example.powerof10.Model + +data class Feed ( +val url: String, +val title: String, +val link: String, +val author: String, +val description: String, +val image: String +) \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/Model/Item.kt b/Dissertation/app/src/main/java/com/example/powerof10/Model/Item.kt new file mode 100644 index 0000000..83f8da2 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/Model/Item.kt @@ -0,0 +1,14 @@ +package com.example.powerof10.Model + +data class Item ( + val title: String, + val pubDate: String, + val link: String, + val guid: String, + val author: String, + val thumbnail: String, + val description: String, + val content: String, + val enclosure: Any, + val categories: List +) \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/Model/RSSObject.kt b/Dissertation/app/src/main/java/com/example/powerof10/Model/RSSObject.kt new file mode 100644 index 0000000..deb509a --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/Model/RSSObject.kt @@ -0,0 +1,7 @@ +package com.example.powerof10.Model + +data class RSSObject( + val status: String, + val feed: Feed, + val items: List +) \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/NavGraph.kt b/Dissertation/app/src/main/java/com/example/powerof10/NavGraph.kt new file mode 100644 index 0000000..e698888 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/NavGraph.kt @@ -0,0 +1,77 @@ +package com.example.powerof10 + +import ExpandableListViewModel +import androidx.compose.runtime.Composable +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.navArgument + + +@Composable +fun SetupNavGraph( + navController: NavHostController, + viewModel: ExpandableListViewModel +) { + NavHost(navController = navController, startDestination = Screen.HomepageRSS.route) + { + composable( + route = Screen.Welcome.route + ) { + Homescreen(navController) + } + composable( + route = Screen.HomepageRSS.route + ) { + + DisplayRssHomepage(navController) + } + composable( + route = Screen.RankingPage.route + ) { + + DisplayRankingPage(navController) + } + composable( + route = Screen.AthleteSearch.route + ) { + + AthleteSearch(navController) + } + composable( + route = "Article/{link}",//parameter for the articlewebview composable + arguments = listOf(navArgument("link"){ + type = NavType.StringType + nullable = false + }) + ) { + var id = requireNotNull(it.arguments?.getString("link").toString()) + ArticleWebView(id) + + } + + composable( + route = "Profile/{profile}",//parameter for the AthleteProfile composable + arguments = listOf(navArgument("profile"){ + type = NavType.StringType + nullable = false + }) + ) { + var id = requireNotNull(it.arguments?.getString("profile").toString()) + AthleteProfile(navController,viewModel,id) + + } + composable( + route = "Chart/{profile}",//parameter for the ChartsPage composable + arguments = listOf(navArgument("profile"){ + type = NavType.StringType + nullable = false + }) + ) { + var id = requireNotNull(it.arguments?.getString("profile").toString()) + ChartsPage(navController,id) + + } + } +} \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/NavigationOptions.kt b/Dissertation/app/src/main/java/com/example/powerof10/NavigationOptions.kt new file mode 100644 index 0000000..e13a6ec --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/NavigationOptions.kt @@ -0,0 +1,9 @@ +package com.example.powerof10 + +object NavigationOptions{ + val nav = listOf( + MenuItems(1, "Home",Screen.HomepageRSS.route), + MenuItems(2, "Rankings",Screen.RankingPage.route), + MenuItems(3, "Athlete Search",Screen.AthleteSearch.route) + ) +} diff --git a/Dissertation/app/src/main/java/com/example/powerof10/RSSArticle.kt b/Dissertation/app/src/main/java/com/example/powerof10/RSSArticle.kt new file mode 100644 index 0000000..42cd979 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/RSSArticle.kt @@ -0,0 +1,53 @@ +package com.example.powerof10 + +import android.os.Bundle +import android.webkit.WebView +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.navigation.NavController +import com.example.powerof10.ui.theme.PowerOf10Theme +import com.google.accompanist.web.WebView +import com.google.accompanist.web.rememberWebViewState + +class RSSArticle : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + PowerOf10Theme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + + } + } + } + } +} + +@Composable +fun ArticleWebView( link: String) { + val state = rememberWebViewState(link) + + WebView( + state, + onCreated = { + it.settings.javaScriptEnabled = true}, + ) +} + +@Preview(showBackground = true) +@Composable +fun DefaultPreview4() { + PowerOf10Theme { + + } +} \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/RankingPage.kt b/Dissertation/app/src/main/java/com/example/powerof10/RankingPage.kt new file mode 100644 index 0000000..97345bf --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/RankingPage.kt @@ -0,0 +1,536 @@ +package com.example.powerof10 + +import android.annotation.SuppressLint +import android.net.Uri +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Devices +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavController +import androidx.navigation.compose.rememberNavController +import com.example.powerof10.Components.NavDrawerBody +import com.example.powerof10.ui.theme.PowerOf10Theme +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.select.Elements +import java.net.URLEncoder +import java.nio.charset.StandardCharsets + +class RankingPage : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + PowerOf10Theme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colors.background + ) { + + } + } + } + } +} + + +suspend fun getHTML(event: String, group: String, sex: String, year: String, area: String="all regions"): Document { + val html: String + val gender = when(sex){ + "Men" -> "M" + else -> {"W"} + }//Translates the gender selected text into power of 10 variable + val age = when(group){ + "Under 20" -> "U20" + "Under 17" -> "U17" + "Under 15" -> "U15" + "Under 13" -> "U13" + + else -> {"All"} + }//Translates the age selected text into power of 10 variable + val region=when(area){ + "East" -> "66" + "East Midlands"-> "65" + "England" -> "91" + "London"-> "67" + "North East" -> "61" + "N Ireland" -> "94" + "North West" -> "63" + "Scotland" ->"92" + "South East" -> "68" + "South West" -> "69" + "Wales" -> "93" + "West Midlands" -> "64" + "Yorkshire" -> "62" + else -> {null} + }//Translates the region selected text into power of 10 variable + html = if (region != null){ //checks if the region url needs to be used instead + "https://www.thepowerof10.info/rankings/rankinglist.aspx?event=$event&agegroup=$age&sex=$gender&year=$year&areaid=$region" + }else{ + "https://www.thepowerof10.info/rankings/rankinglist.aspx?event=$event&agegroup=$age&sex=$gender&year=$year" + } + + return withContext(Dispatchers.IO) { + return@withContext Jsoup.connect(html).get() + } + +} + + +fun getNames(doc: Document): Elements { + val records = doc.getElementsByAttributeValueContaining("class", "rlr") + val cleanList = Elements() + records.forEach { + if (it.child(6).childNodeSize()>0){ + cleanList.add(it) + } + } + return cleanList +} //Cleans the HTML by removing the records that are blank (with no name) + + + + + +//@Preview(name = "NEXUS_7_2013", device = Devices.NEXUS_7_2013) +//@Preview(name = "NEXUS_5", device = Devices.NEXUS_5) +//@Preview(name = "NEXUS_6", device = Devices.NEXUS_6) +//@Preview(name = "NEXUS_9", device = Devices.NEXUS_9) +//@Preview(name = "NEXUS_10", device = Devices.NEXUS_10) +//@Preview(name = "NEXUS_5X", device = Devices.NEXUS_5X) +//@Preview(name = "NEXUS_6P", device = Devices.NEXUS_6P) +//@Preview(name = "PIXEL_C", device = Devices.PIXEL_C) +//@Preview(name = "PIXEL", device = Devices.PIXEL) +//@Preview(name = "PIXEL_XL", device = Devices.PIXEL_XL) +//@Preview(name = "PIXEL_2", device = Devices.PIXEL_2) +//@Preview(name = "PIXEL_2_XL", device = Devices.PIXEL_2_XL) +//@Preview(name = "PIXEL_3", device = Devices.PIXEL_3) +//@Preview(name = "PIXEL_3_XL", device = Devices.PIXEL_3_XL) +//@Preview(name = "PIXEL_3A", device = Devices.PIXEL_3A) +//@Preview(name = "PIXEL_3A_XL", device = Devices.PIXEL_3A_XL) + +@SuppressLint("MutableCollectionMutableState", "UnusedMaterialScaffoldPaddingParameter") +@Preview(name = "PIXEL_4", device = Devices.PIXEL_4) +//@Preview(name = "PIXEL_4_XL", device = Devices.PIXEL_4_XL) +@Composable +fun UserProfilePreview2(){ + DisplayRankingPage(navController = rememberNavController()) +} + + +@SuppressLint("UnusedMaterialScaffoldPaddingParameter") +@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) +@Composable +fun DisplayRankingPage(navController: NavController) { + //following observable variable descriptions apply to all + var yearExpanded by remember { mutableStateOf(false) }//checks the state of the dropdown (expanded or not) + val yearOptions = listOf("2023","2022", "2021", "2020", "2019", "2018", "2017", "2016", "2015", "2014", "2013", "2012", "2011", "2010", "2009", "2008", "2007", "2006", "All Time")//variables inside the menu + var yearSelectedOptionText by remember { mutableStateOf(yearOptions[0]) }//the current object that's selected + + var regionExpanded by remember { mutableStateOf(false) } + val regionOptions = listOf("UK","East","East Midlands","England","London","North East","N Ireland","North West","Scotland","South East","South West","Wales","West Midlands","Yorkshire") + var regionSelectedOptionText by remember { mutableStateOf(regionOptions[0]) } + + var genderExpanded by remember { mutableStateOf(false) } + val genderOptions = listOf("Men","Women") + var genderSelectedOptionText by remember { mutableStateOf(genderOptions[0]) } + + var groupExpanded by remember { mutableStateOf(false) } + val groupOptions = listOf("Overall","Under 20","Under 17","Under 15","Under 13") + var groupSelectedOptionText by remember { mutableStateOf(groupOptions[0]) } + + + var eventExpanded by remember { mutableStateOf(false) } + var eventSelectedOptionText by remember { mutableStateOf("60") } + val eventOptionsMenOverall = listOf("60","100","200","400","800","1500","3000","5000","10000","3000SC","10K","Half Marathon","Marathon","60 Hurdles","110 Hurdles","400 Hurdles","High Jump","Pole Vault","Long Jump","Triple Jump","Shot","Discus","Hammer","Javelin","Indoor Hep","Decathlon","20K Walk","35K Walk","4x100","4x400") + val eventOptionsMenUnder20 = listOf("60","100","200","400","800","1500","3000","5000","2000SC","60 Hurdles","110 Hurdles","400 Hurdles","High Jump","Pole Vault","Long Jump","Triple Jump","Shot","Discus","Hammer","Javelin","Indoor Hep","Decathlon","4x100","4x400") + val eventOptionsMenUnder17 = listOf("60","100","200","400","800","1500","3000","1500SC","60 Hurdles","100 Hurdles","400 Hurdles","High Jump","Pole Vault","Long Jump","Triple Jump","Shot","Discus","Hammer","Javelin","Indoor Hep","Octathlon","Decathlon","4x100","4x400") + val eventOptionsMenUnder15 = listOf("60","100","200","300","800","1500","3000","60 Hurdles","80 Hurdles","High Jump","Pole Vault","Long Jump","Triple Jump","Shot","Discus","Hammer","Javelin","Indoor Pen","Pentathlon","4x100","4x300") + val eventOptionsMenUnder13 = listOf("60","75","100","150","200","600","800","1200","1500","60 Hurdles","75 Hurdles","High Jump","Pole Vault","Long Jump","Shot","Discus","Javelin","Pentathlon","4x100") + + val eventOptionsWomenOverall = listOf(" 60","100","200","400","800","1500","3000","5000","10000","2000SC","3000SC","10K","Half Marathon","Marathon","60 Hurdles","100 Hurdles","400 Hurdles","High Jump","Pole Vault","Long Jump","Triple Jump","Shot","Discus","Hammer","Javelin","Indoor Pen","Heptathlon","20K Walk","35K Walk","4x100","4x400") + val eventOptionsWomenUnder20 = listOf("60","100","200","400","800","1500","3000","5000","1500SC","60 Hurdles","100 Hurdles","400 Hurdles","High Jump","Pole Vault","Long Jump","Triple Jump","Shot","Discus","Hammer","Javelin","Indoor Pen","Heptathlon","4x100","4x400") + val eventOptionsWomenUnder17 = listOf("60","100","200","300","800","1500","3000","1500SC","60 Hurdles","80 Hurdles","300 Hurdles","High Jump","Pole Vault","Long Jump","Triple Jump","Shot","Discus","Hammer","Javelin","Indoor Pen","Heptathlon","4x100","4x300") + val eventOptionsWomenUnder15 = listOf("60","100","200","300","800","1500","3000","60 Hurdles","75 Hurdles","High Jump","Pole Vault","Long Jump","Triple Jump","Shot","Discus","Hammer","Javelin","Indoor Pen","Pentathlon","4x100","4x300") + val eventOptionsWomenUnder13 = listOf("60","75","100","150","200","600","800","1200","1500","60 Hurdles","70 Hurdles","High Jump","Pole Vault","Long Jump","Shot","Discus","Javelin","Pentathlon","4x100") + + var updateOptions by remember { mutableStateOf(0) }//updates the available options when a dropdown changes + var buttonPress by remember { mutableStateOf(0) }//updates the lazy column when the apply button is pressed + var athletesList by remember {mutableStateOf(Elements()) }//the list of records for each athlete + + //chooses which event list to show depending on the values of the menus + val subList = remember(updateOptions) { + if (groupSelectedOptionText == "Overall" && genderSelectedOptionText == "Men"){ + eventOptionsMenOverall + }else if(groupSelectedOptionText == "Under 20" && genderSelectedOptionText == "Men"){ + eventOptionsMenUnder20 + }else if(groupSelectedOptionText == "Under 17" && genderSelectedOptionText == "Men"){ + eventOptionsMenUnder17 + }else if(groupSelectedOptionText == "Under 15" && genderSelectedOptionText == "Men"){ + eventOptionsMenUnder15 + }else if(groupSelectedOptionText == "Under 13" && genderSelectedOptionText == "Men"){ + eventOptionsMenUnder13 + }else if(groupSelectedOptionText == "Overall" && genderSelectedOptionText == "Women"){ + eventOptionsWomenOverall + }else if(groupSelectedOptionText == "Under 20" && genderSelectedOptionText == "Women"){ + eventOptionsWomenUnder20 + }else if(groupSelectedOptionText == "Under 17" && genderSelectedOptionText == "Women"){ + eventOptionsWomenUnder17 + }else if(groupSelectedOptionText == "Under 15" && genderSelectedOptionText == "Women"){ + eventOptionsWomenUnder15 + }else if(groupSelectedOptionText == "Under 13" && genderSelectedOptionText == "Women"){ + eventOptionsWomenUnder13 + } + else{ + listOf("Please select all options first") + } + + } + val scaffoldState = rememberScaffoldState() + val scope = rememberCoroutineScope() + val powerOfTen = painterResource(id = R.drawable.powerof10) + LaunchedEffect(buttonPress) { //recomposes on button press + val html = getHTML(eventSelectedOptionText,groupSelectedOptionText,genderSelectedOptionText,yearSelectedOptionText,regionSelectedOptionText)//gets html from the selected page + athletesList = getNames(html)//gets records from page + + + } + Scaffold( + scaffoldState = scaffoldState, + topBar = { + CenterAlignedTopAppBar(modifier = Modifier.fillMaxWidth(), + title = { + Image( + painter = powerOfTen, + contentDescription = "Power of Ten Logo", + modifier = Modifier.padding(5.dp), + contentScale = ContentScale.FillBounds + ) + }, + colors = TopAppBarDefaults.centerAlignedTopAppBarColors( + Color(0xFFE5383B), + navigationIconContentColor = Color.White + ), + navigationIcon = { + IconButton(onClick = { scope.launch { scaffoldState.drawerState.open() } }) { + Icon( + Icons.Filled.Menu, contentDescription = null, + tint = Color.White, + modifier = Modifier.size(48.dp) + ) + } + } + ) + }, + drawerContent = { + NavDrawerBody(items = NavigationOptions.nav, onItemClick = { navController.navigate(route =it.route) } + ) + }, content = { + Column(modifier = Modifier.fillMaxWidth()) { + + + + + LazyColumn(content = { + item { + Text( + text = "Rankings", + fontWeight = FontWeight.Bold, + fontSize = 24.sp, + modifier = Modifier.padding(10.dp) + ) + Row( + horizontalArrangement = Arrangement.SpaceAround, + modifier = Modifier.padding(0.dp, 0.dp, 0.dp, 10.dp) + ) { + ExposedDropdownMenuBox( + expanded = yearExpanded, + modifier = Modifier + .weight(1f) + .padding(horizontal = 10.dp, vertical = 0.dp), + onExpandedChange = { yearExpanded = !yearExpanded }, + ) { + TextField( + readOnly = true, + value = yearSelectedOptionText, + onValueChange = {}, + label = { Text("1. Year") }, + colors = ExposedDropdownMenuDefaults.textFieldColors(), + ) + ExposedDropdownMenu( + expanded = yearExpanded, + onDismissRequest = { yearExpanded = false }, + ) { + yearOptions.forEach { yearSelectionOption -> + DropdownMenuItem( + onClick = { + yearSelectedOptionText = yearSelectionOption + yearExpanded = false + } + ) { Text(text = yearSelectionOption) } + } + } + } + + + ExposedDropdownMenuBox( + expanded = regionExpanded, + modifier = Modifier + .weight(1f) + .padding(horizontal = 10.dp, vertical = 0.dp), + onExpandedChange = { regionExpanded = !regionExpanded }, + ) { + TextField( + readOnly = true, + value = regionSelectedOptionText, + onValueChange = {}, + label = { Text("2. Region") }, + colors = ExposedDropdownMenuDefaults.textFieldColors(), + ) + ExposedDropdownMenu( + expanded = regionExpanded, + onDismissRequest = { regionExpanded = false }, + ) { + regionOptions.forEach { regionSelectionOption -> + DropdownMenuItem( + onClick = { + regionSelectedOptionText = regionSelectionOption + regionExpanded = false + } + ) { Text(text = regionSelectionOption) } + } + } + } + + ExposedDropdownMenuBox( + expanded = genderExpanded, + modifier = Modifier + .weight(1f) + .padding(horizontal = 10.dp, vertical = 0.dp), + onExpandedChange = { genderExpanded = !genderExpanded }, + ) { + TextField( + readOnly = true, + value = genderSelectedOptionText, + onValueChange = { + }, + label = { Text("3. Gender") }, + colors = ExposedDropdownMenuDefaults.textFieldColors(), + ) + ExposedDropdownMenu( + expanded = genderExpanded, + onDismissRequest = { genderExpanded = false }, + ) { + genderOptions.forEach { selectionOption -> + DropdownMenuItem( + onClick = { + genderSelectedOptionText = selectionOption + groupSelectedOptionText="Overall" + eventSelectedOptionText="60" + updateOptions += 1 + + + genderExpanded = false + } + ) { Text(text = selectionOption) } + } + } + } + } + Row( + horizontalArrangement = Arrangement.SpaceAround, modifier = Modifier + .fillMaxWidth() + .padding(0.dp, 0.dp, 0.dp, 10.dp) + ) { + ExposedDropdownMenuBox( + expanded = groupExpanded, + modifier = Modifier + .weight(1f) + .padding(horizontal = 10.dp, vertical = 0.dp), + onExpandedChange = { groupExpanded = !groupExpanded }, + ) { + TextField( + readOnly = true, + value = groupSelectedOptionText, + onValueChange = {}, + label = { Text("4. Group") }, + colors = ExposedDropdownMenuDefaults.textFieldColors(), + ) + ExposedDropdownMenu( + expanded = groupExpanded, + onDismissRequest = { groupExpanded = false }, + ) { + groupOptions.forEach { selectionOption -> + DropdownMenuItem( + onClick = { + groupSelectedOptionText = selectionOption + eventSelectedOptionText="60" + updateOptions += 1 + groupExpanded = false + } + ) { Text(text = selectionOption) } + } + } + } + + ExposedDropdownMenuBox( + expanded = eventExpanded, + modifier = Modifier + .weight(1f) + .padding(horizontal = 10.dp, vertical = 0.dp), + onExpandedChange = { eventExpanded = !eventExpanded }, + ) { + TextField( + readOnly = true, + value = eventSelectedOptionText, + onValueChange = {}, + label = { Text("5. Event") }, + colors = ExposedDropdownMenuDefaults.textFieldColors(), + ) + ExposedDropdownMenu( + expanded = eventExpanded, + onDismissRequest = { eventExpanded = false }, + ) { + subList.forEach { selectionOption -> + DropdownMenuItem( + onClick = { + eventSelectedOptionText = selectionOption + updateOptions += 1 + eventExpanded = false + } + ) { Text(text = selectionOption) } + } + } + } + + Button( + onClick = { buttonPress += 1 }, modifier = Modifier + .weight(1f) + .padding(horizontal = 10.dp, vertical = 0.dp) + ) { + Text("Apply") + + } + + } + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth().padding(horizontal = 5.dp) + ) { + Text(text = "Rank") + Text(text = "Club") + Text(text = "Performance") + } + } + items(athletesList.size) { + + + RankingItem(it, athletesList,navController) + } + }) + + + } + + })//Dropdown Menus +} + +@Composable +fun RankingItem(index: Int, athleteList: Elements,navController: NavController){ + val background: Color = if (index%2==1){ + Color(0xFFD3D3D3) + } + else { + Color(0xFFF5F3F4) + } //alternates the background colour + val rank = athleteList[index].child(0).text() + val fName = athleteList[index].child(6).child(0).text().split("\\s".toRegex())[0] + val sName = athleteList[index].child(6).child(0).text().split("\\s".toRegex())[1] + + + val mContext = LocalContext.current +Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier + .background(background) + .fillMaxWidth() + .clickable { + val link = Uri.parse(athleteList[index].child(6).child(0).attr("href").substringAfter("athletes/")).toString() + val encodedUrl = URLEncoder.encode(link, StandardCharsets.UTF_8.toString())//cant pass url as params without encoding first + navController.navigate("Profile/$encodedUrl")//navigates to profile instead of browser +// val browserIntent = Intent( +// Intent.ACTION_VIEW, +// Uri.parse( +// "https://www.thepowerof10.info/" + athleteList[index] +// .child(6) +// .child(0) +// .attr("href") +// ) +// ) +// browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)// Starts browser intent to athlete profile +// mContext.startActivity(browserIntent) + }) { + Column(verticalArrangement = Arrangement.SpaceBetween,modifier = Modifier.fillMaxWidth(0.08f)) { + Text(text = rank ) + } + + Column(verticalArrangement = Arrangement.SpaceBetween,modifier = Modifier.fillMaxWidth(0.17f)) { + Text(text = fName, + maxLines = 1, + overflow = TextOverflow.Ellipsis) + Text(text = sName, + maxLines = 1, + overflow = TextOverflow.Ellipsis) + Text(text = "("+athleteList[index].child(4).text()+")",//athlete's PB + maxLines = 1, + overflow = TextOverflow.Ellipsis) + } + + Column(verticalArrangement = Arrangement.SpaceBetween,modifier = Modifier.fillMaxWidth(0.35f)) { + Text(text = athleteList[index].child(10).text(),//Athlete's club + maxLines = 1, + overflow = TextOverflow.Ellipsis) + Text(text = athleteList[index].child(9).text(),//athletes coach + maxLines = 1, + overflow = TextOverflow.Ellipsis) + + } + Column(modifier = Modifier.fillMaxWidth(0.4f)) { + Text(text = athleteList[index].child(1).text() ,//Athlete's SB + maxLines = 1, + overflow = TextOverflow.Ellipsis) + Text(text = athleteList[index].child(11).text(),//Athletes SB Location + maxLines = 1, + overflow = TextOverflow.Ellipsis) + Text(text = athleteList[index].child(12).text(),//Performance date + maxLines = 1, + overflow = TextOverflow.Ellipsis) + } +} + +} + + diff --git a/Dissertation/app/src/main/java/com/example/powerof10/Screen.kt b/Dissertation/app/src/main/java/com/example/powerof10/Screen.kt new file mode 100644 index 0000000..bf34377 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/Screen.kt @@ -0,0 +1,10 @@ +package com.example.powerof10 + +sealed class Screen(val route: String) { + object Welcome: Screen(route ="welcome" ) + object HomepageRSS: Screen(route = "Homepage_rss") + object RankingPage: Screen(route = "RankingPage") + object AthleteSearch: Screen(route = "AthleteSearch") + object RSSArticle: Screen(route = "RSSArticle") + object ChartsPage: Screen(route = "ChartsPage") +} diff --git a/Dissertation/app/src/main/java/com/example/powerof10/WelcomeScreen.kt b/Dissertation/app/src/main/java/com/example/powerof10/WelcomeScreen.kt new file mode 100644 index 0000000..bdb4a78 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/WelcomeScreen.kt @@ -0,0 +1,83 @@ +package com.example.powerof10 + +import android.content.Intent +import android.os.Bundle +import android.provider.AlarmClock.EXTRA_MESSAGE +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Button +import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.zIndex +import androidx.navigation.NavController +import androidx.navigation.fragment.findNavController + + + + + +@Composable +fun Homescreen(navController: NavController) { + val background = painterResource(id = R.drawable.teamgb) + val powerOfTen = painterResource(id = R.drawable.powerof10) + Box( + modifier = Modifier.wrapContentSize(Alignment.Center) + ) { + Image( + painter = background, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxHeight() + ) + + Box( + modifier = Modifier + .fillMaxSize() + .padding(0.dp, 70.dp, 0.dp, 0.dp) + + ) { + Image( + painter = powerOfTen, + contentDescription = "Power of Ten Logo", + modifier = Modifier.scale(1.5f) + .align(Alignment.TopCenter) + ) + Button( + onClick = {navController.navigate(route = Screen.HomepageRSS.route)}, modifier = Modifier + .zIndex(1f) + .align(Alignment.BottomCenter) + .padding(0.dp, 0.dp, 0.dp, 80.dp) + .width(270.dp) + .height(70.dp), + colors = ButtonDefaults.buttonColors( + backgroundColor = Color(0xFFE5383B), + contentColor = Color.White + ), + shape = RoundedCornerShape(50) + ) { + Text(text = "Welcome", fontSize = 20.sp) + + } + } + + + } +} + diff --git a/Dissertation/app/src/main/java/com/example/powerof10/ui/theme/Color.kt b/Dissertation/app/src/main/java/com/example/powerof10/ui/theme/Color.kt new file mode 100644 index 0000000..2f99746 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/ui/theme/Color.kt @@ -0,0 +1,8 @@ +package com.example.powerof10.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple200 = Color(0xFFBB86FC) +val Purple500 = Color(0xFF6200EE) +val Purple700 = Color(0xFF3700B3) +val Teal200 = Color(0xFF03DAC5) \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/ui/theme/Shape.kt b/Dissertation/app/src/main/java/com/example/powerof10/ui/theme/Shape.kt new file mode 100644 index 0000000..78f1c09 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/ui/theme/Shape.kt @@ -0,0 +1,11 @@ +package com.example.powerof10.ui.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val Shapes = Shapes( + small = RoundedCornerShape(4.dp), + medium = RoundedCornerShape(4.dp), + large = RoundedCornerShape(0.dp) +) \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/ui/theme/Theme.kt b/Dissertation/app/src/main/java/com/example/powerof10/ui/theme/Theme.kt new file mode 100644 index 0000000..82d29ff --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/ui/theme/Theme.kt @@ -0,0 +1,44 @@ +package com.example.powerof10.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +private val DarkColorPalette = darkColors( + primary = Purple200, + primaryVariant = Purple700, + secondary = Teal200 +) + +private val LightColorPalette = lightColors( + primary = Purple500, + primaryVariant = Purple700, + secondary = Teal200 + + /* Other default colors to override + background = Color.White, + surface = Color.White, + onPrimary = Color.White, + onSecondary = Color.Black, + onBackground = Color.Black, + onSurface = Color.Black, + */ +) + +@Composable +fun PowerOf10Theme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colors = colors, + typography = Typography, + shapes = Shapes, + content = content + ) +} \ No newline at end of file diff --git a/Dissertation/app/src/main/java/com/example/powerof10/ui/theme/Type.kt b/Dissertation/app/src/main/java/com/example/powerof10/ui/theme/Type.kt new file mode 100644 index 0000000..e631f59 --- /dev/null +++ b/Dissertation/app/src/main/java/com/example/powerof10/ui/theme/Type.kt @@ -0,0 +1,28 @@ +package com.example.powerof10.ui.theme + +import androidx.compose.material.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + body1 = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp + ) + /* Other default text styles to override + button = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.W500, + fontSize = 14.sp + ), + caption = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 12.sp + ) + */ +) \ No newline at end of file diff --git a/Dissertation/app/src/main/res/drawable-nodpi/gradient_background.xml b/Dissertation/app/src/main/res/drawable-nodpi/gradient_background.xml new file mode 100644 index 0000000..4b3ee7d --- /dev/null +++ b/Dissertation/app/src/main/res/drawable-nodpi/gradient_background.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/Dissertation/app/src/main/res/drawable-nodpi/powerof10.png b/Dissertation/app/src/main/res/drawable-nodpi/powerof10.png new file mode 100644 index 0000000..464042e Binary files /dev/null and b/Dissertation/app/src/main/res/drawable-nodpi/powerof10.png differ diff --git a/Dissertation/app/src/main/res/drawable-nodpi/teamgb.png b/Dissertation/app/src/main/res/drawable-nodpi/teamgb.png new file mode 100644 index 0000000..3c7a006 Binary files /dev/null and b/Dissertation/app/src/main/res/drawable-nodpi/teamgb.png differ diff --git a/Dissertation/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/Dissertation/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/Dissertation/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Dissertation/app/src/main/res/drawable/background.jpg b/Dissertation/app/src/main/res/drawable/background.jpg new file mode 100644 index 0000000..32776cf Binary files /dev/null and b/Dissertation/app/src/main/res/drawable/background.jpg differ diff --git a/Dissertation/app/src/main/res/drawable/ic_launcher_background.xml b/Dissertation/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/Dissertation/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Dissertation/app/src/main/res/layout/activity_main.xml b/Dissertation/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..f7829db --- /dev/null +++ b/Dissertation/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Dissertation/app/src/main/res/layout/fragment_home_page_rss.xml b/Dissertation/app/src/main/res/layout/fragment_home_page_rss.xml new file mode 100644 index 0000000..e7d2e83 --- /dev/null +++ b/Dissertation/app/src/main/res/layout/fragment_home_page_rss.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + diff --git a/Dissertation/app/src/main/res/layout/fragment_welcome_screen.xml b/Dissertation/app/src/main/res/layout/fragment_welcome_screen.xml new file mode 100644 index 0000000..59c44f5 --- /dev/null +++ b/Dissertation/app/src/main/res/layout/fragment_welcome_screen.xml @@ -0,0 +1,14 @@ + + + + + + + \ No newline at end of file diff --git a/Dissertation/app/src/main/res/layout/row.xml b/Dissertation/app/src/main/res/layout/row.xml new file mode 100644 index 0000000..4ac9c2a --- /dev/null +++ b/Dissertation/app/src/main/res/layout/row.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dissertation/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/Dissertation/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/Dissertation/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Dissertation/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/Dissertation/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/Dissertation/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Dissertation/app/src/main/res/mipmap-hdpi/ic_launcher.png b/Dissertation/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..7bab574 Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/Dissertation/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/Dissertation/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..4f1e836 Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/Dissertation/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/Dissertation/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..b29756d Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/Dissertation/app/src/main/res/mipmap-mdpi/ic_launcher.png b/Dissertation/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..dd59f3f Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/Dissertation/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/Dissertation/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..84c051d Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/Dissertation/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/Dissertation/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..49fcf9c Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/Dissertation/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/Dissertation/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..5cafd22 Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/Dissertation/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/Dissertation/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..48b4e9d Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/Dissertation/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/Dissertation/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..c529a1c Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/Dissertation/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/Dissertation/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..6fb221d Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/Dissertation/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/Dissertation/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..fdf466d Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/Dissertation/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/Dissertation/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..da0549b Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/Dissertation/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/Dissertation/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..170660b Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/Dissertation/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/Dissertation/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..aadee5b Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/Dissertation/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/Dissertation/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..642a59d Binary files /dev/null and b/Dissertation/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/Dissertation/app/src/main/res/navigation/navigation.xml b/Dissertation/app/src/main/res/navigation/navigation.xml new file mode 100644 index 0000000..d59fa81 --- /dev/null +++ b/Dissertation/app/src/main/res/navigation/navigation.xml @@ -0,0 +1,22 @@ + + + + + + + + \ No newline at end of file diff --git a/Dissertation/app/src/main/res/values/colors.xml b/Dissertation/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..ddaea49 --- /dev/null +++ b/Dissertation/app/src/main/res/values/colors.xml @@ -0,0 +1,11 @@ + + + #FFBB86FC + #FFE5383B + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/Dissertation/app/src/main/res/values/ic_launcher_background.xml b/Dissertation/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..3725ba7 --- /dev/null +++ b/Dissertation/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #E5383B + \ No newline at end of file diff --git a/Dissertation/app/src/main/res/values/strings.xml b/Dissertation/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..1fbc058 --- /dev/null +++ b/Dissertation/app/src/main/res/values/strings.xml @@ -0,0 +1,14 @@ + + Power Of 10 + + Hello blank fragment + HomeRSS + RSSFEED + RankingPage + NavDrawer + AthleteSearch + RSSArticle + AthleteProfile + components + AnalysisTool + \ No newline at end of file diff --git a/Dissertation/app/src/main/res/values/themes.xml b/Dissertation/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..e990fa0 --- /dev/null +++ b/Dissertation/app/src/main/res/values/themes.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/Dissertation/app/src/test/java/com/example/powerof10/ExampleUnitTest.kt b/Dissertation/app/src/test/java/com/example/powerof10/ExampleUnitTest.kt new file mode 100644 index 0000000..7a3cd2d --- /dev/null +++ b/Dissertation/app/src/test/java/com/example/powerof10/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.example.powerof10 + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/Dissertation/build.gradle b/Dissertation/build.gradle new file mode 100644 index 0000000..1205eb9 --- /dev/null +++ b/Dissertation/build.gradle @@ -0,0 +1,24 @@ +buildscript { + ext.kotlin_version = '1.8.10' + ext { + compose_version = '1.4.2' + navigationVersion = '2.5.3' + + } + repositories { + mavenCentral() + maven { url 'https://jitpack.io' } + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +}// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id 'com.android.application' version '7.4.2' apply false + id 'com.android.library' version '7.4.2' apply false + id 'org.jetbrains.kotlin.android' version '1.6.21' apply false +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/Dissertation/gradle.properties b/Dissertation/gradle.properties new file mode 100644 index 0000000..cd0519b --- /dev/null +++ b/Dissertation/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/Dissertation/gradle/wrapper/gradle-wrapper.jar b/Dissertation/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/Dissertation/gradle/wrapper/gradle-wrapper.jar differ diff --git a/Dissertation/gradle/wrapper/gradle-wrapper.properties b/Dissertation/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..5cd84b2 --- /dev/null +++ b/Dissertation/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon May 23 03:31:55 BST 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/Dissertation/gradlew b/Dissertation/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/Dissertation/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/Dissertation/gradlew.bat b/Dissertation/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/Dissertation/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/Dissertation/rsspage/.gitignore b/Dissertation/rsspage/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/Dissertation/rsspage/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/Dissertation/rsspage/build.gradle b/Dissertation/rsspage/build.gradle new file mode 100644 index 0000000..e30a90d --- /dev/null +++ b/Dissertation/rsspage/build.gradle @@ -0,0 +1,50 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +android { + compileSdk 32 + + defaultConfig { + applicationId "com.example.powerof10" + minSdk 21 + targetSdk 32 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.7.0' + implementation 'androidx.appcompat:appcompat:1.4.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.2' + implementation 'com.google.code.gson:gson:2.8.9' + implementation 'androidx.recyclerview:recyclerview:1.3.0-alpha01' + implementation 'androidx.cardview:cardview:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.3.0-alpha01' + implementation 'org.jsoup:jsoup:1.10.3' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + implementation 'com.prof18.rssparser:rssparser:3.1.3' + implementation 'com.squareup.picasso:picasso:2.8' +} \ No newline at end of file diff --git a/Dissertation/rsspage/proguard-rules.pro b/Dissertation/rsspage/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/Dissertation/rsspage/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/Dissertation/rsspage/src/androidTest/java/com/example/rssfeed/ExampleInstrumentedTest.kt b/Dissertation/rsspage/src/androidTest/java/com/example/rssfeed/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..d2be951 --- /dev/null +++ b/Dissertation/rsspage/src/androidTest/java/com/example/rssfeed/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.rssfeed + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.rssfeed", appContext.packageName) + } +} \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/AndroidManifest.xml b/Dissertation/rsspage/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9ebceb7 --- /dev/null +++ b/Dissertation/rsspage/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/java/com/example/rssfeed/Adapter/FeedAdapter.kt b/Dissertation/rsspage/src/main/java/com/example/rssfeed/Adapter/FeedAdapter.kt new file mode 100644 index 0000000..864d72e --- /dev/null +++ b/Dissertation/rsspage/src/main/java/com/example/rssfeed/Adapter/FeedAdapter.kt @@ -0,0 +1,87 @@ +package com.example.rssfeed.Adapter + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView +import com.example.rssfeed.Interface.ItemClickListener +import com.example.rssfeed.Model.RSSObject +import com.example.rssfeed.R +import com.squareup.picasso.Picasso +import org.jsoup.Jsoup +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element + +class FeedViewHolder(itemView:View):RecyclerView.ViewHolder(itemView),View.OnClickListener,View.OnLongClickListener { + + var txtTitle: TextView + var image: ImageView + var txtContent: TextView + + private var itemClickListener: ItemClickListener?=null + init { + txtContent = itemView.findViewById(R.id.txtContent) + txtTitle = itemView.findViewById(R.id.txtTitle) + image = itemView.findViewById(R.id.image) + + itemView.setOnClickListener(this) + itemView.setOnLongClickListener(this) + } + + fun setItemClickListener(itemClickListener: ItemClickListener) + { + this.itemClickListener = itemClickListener + } + + override fun onClick(v: View?) { + itemClickListener!!.onClick(v,adapterPosition,false) + } + + override fun onLongClick(v: View?): Boolean { + itemClickListener!!.onClick(v,adapterPosition,true) + return true + } +} +fun extractP(description: String):String{ + val doc: Document = Jsoup.parse(description) + val p : Element = doc.select("p").first() + return p.text() + +} +class FeedAdapter (private val rssObject: RSSObject, private val mContenxt: Context):RecyclerView.Adapter() +{ + private val inflater: LayoutInflater = LayoutInflater.from(mContenxt) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedViewHolder { + val itemView = inflater.inflate(R.layout.row,parent,false) + return FeedViewHolder(itemView) + } + + + override fun onBindViewHolder(holder: FeedViewHolder, position: Int) { + holder.txtTitle.text = rssObject.items[position].title + // holder.image.drawable = Picasso.get().load(rssObject.items[position].thumbnail).in + Picasso.get().load(rssObject.items[position].thumbnail).into(holder.image) + holder.txtContent.text = extractP(rssObject.items[position].description) + + holder.setItemClickListener(ItemClickListener{ view, position, isLongClick -> + + if(!isLongClick) + { + val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(rssObject.items[position].link)) + browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + mContenxt.startActivity(browserIntent) + + } + }) + } + override fun getItemCount(): Int { + return rssObject.items.size + } + +} \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/java/com/example/rssfeed/Common/HTTPDataHandler.java b/Dissertation/rsspage/src/main/java/com/example/rssfeed/Common/HTTPDataHandler.java new file mode 100644 index 0000000..a3b1b17 --- /dev/null +++ b/Dissertation/rsspage/src/main/java/com/example/rssfeed/Common/HTTPDataHandler.java @@ -0,0 +1,41 @@ +package com.example.rssfeed.Common; + +import java.io.BufferedInputStream; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; + +public class HTTPDataHandler { + static String stream=""; + + public HTTPDataHandler () {} + + public String GetHTTPDataHandler(String urlString) + { + try + { + URL url = new URL(urlString); + HttpURLConnection urlConnection = (HttpURLConnection)url.openConnection(); + + if(urlConnection.getResponseCode()== HttpURLConnection.HTTP_OK) + { + InputStream inputStream = new BufferedInputStream(urlConnection.getInputStream()); + + BufferedReader r = new BufferedReader(new InputStreamReader(inputStream)); + StringBuilder sb = new StringBuilder(); + String line =""; + while ((line = r.readLine()) != null) + sb.append(line); + stream = sb.toString(); + urlConnection.disconnect(); + + + } + } catch (Exception ex) { + return null; + } + return stream; + } +} diff --git a/Dissertation/rsspage/src/main/java/com/example/rssfeed/Interface/ItemClickListener.java b/Dissertation/rsspage/src/main/java/com/example/rssfeed/Interface/ItemClickListener.java new file mode 100644 index 0000000..9d75cfe --- /dev/null +++ b/Dissertation/rsspage/src/main/java/com/example/rssfeed/Interface/ItemClickListener.java @@ -0,0 +1,7 @@ +package com.example.rssfeed.Interface; + +import android.view.View; + +public interface ItemClickListener { + void onClick(View view, int position, boolean isLongClick); +} diff --git a/Dissertation/rsspage/src/main/java/com/example/rssfeed/MainActivity.kt b/Dissertation/rsspage/src/main/java/com/example/rssfeed/MainActivity.kt new file mode 100644 index 0000000..a920e1d --- /dev/null +++ b/Dissertation/rsspage/src/main/java/com/example/rssfeed/MainActivity.kt @@ -0,0 +1,72 @@ +package com.example.rssfeed + +import android.annotation.SuppressLint +import android.app.ProgressDialog +import android.os.AsyncTask +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.example.rssfeed.Adapter.FeedAdapter +import com.example.rssfeed.Common.HTTPDataHandler +import com.example.rssfeed.Model.RSSObject +import com.google.gson.Gson +import java.lang.StringBuilder + + + +class MainActivity : AppCompatActivity() { + + //private val RSS_link = "http://rss.nytimes.com/services/xml/rss/nyt/Science.xml" + private val RSS_link = "https://athleticsweekly.com/feed/" + private val RSS_toJsonAPI = "https://api.rss2json.com/v1/api.json?rss_url=" + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + val recyclerView: RecyclerView = findViewById(R.id.recyclerView) + + val linearLayoutManager = LinearLayoutManager(baseContext,LinearLayoutManager.VERTICAL,false) + recyclerView.layoutManager = linearLayoutManager + + loadRSS() + } + + private fun loadRSS() + { + val loadRSSAsync = @SuppressLint("StaticFieldLeak") + object : AsyncTask() { + internal var mDialog = ProgressDialog(this@MainActivity) + + override fun doInBackground(vararg params: String?): String { + val result:String + val http= HTTPDataHandler() + result = http.GetHTTPDataHandler(params[0]) + return result + } + + override fun onPreExecute() { + mDialog.setMessage("Please Wait..") + mDialog.show() + } + + override fun onPostExecute(result: String?) { + val recyclerView: RecyclerView = findViewById(R.id.recyclerView) + mDialog.dismiss() + var rssObject: RSSObject + rssObject = Gson().fromJson(result, RSSObject::class.java!!) + val adapter = FeedAdapter(rssObject,baseContext) + recyclerView.adapter = adapter + adapter.notifyDataSetChanged() + + } + } + + val url_get_data = StringBuilder(RSS_toJsonAPI) + url_get_data.append(RSS_link) + loadRSSAsync.execute(url_get_data.toString()) + } + + +} \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/java/com/example/rssfeed/Model/Feed.kt b/Dissertation/rsspage/src/main/java/com/example/rssfeed/Model/Feed.kt new file mode 100644 index 0000000..40c382d --- /dev/null +++ b/Dissertation/rsspage/src/main/java/com/example/rssfeed/Model/Feed.kt @@ -0,0 +1,10 @@ +package com.example.rssfeed.Model + +data class Feed ( +val url: String, +val title: String, +val link: String, +val author: String, +val description: String, +val image: String +) \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/java/com/example/rssfeed/Model/Item.kt b/Dissertation/rsspage/src/main/java/com/example/rssfeed/Model/Item.kt new file mode 100644 index 0000000..69c0b37 --- /dev/null +++ b/Dissertation/rsspage/src/main/java/com/example/rssfeed/Model/Item.kt @@ -0,0 +1,14 @@ +package com.example.rssfeed.Model + +data class Item ( + val title: String, + val pubDate: String, + val link: String, + val guid: String, + val author: String, + val thumbnail: String, + val description: String, + val content: String, + val enclosure: Any, + val categories: List +) \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/java/com/example/rssfeed/Model/RSSObject.kt b/Dissertation/rsspage/src/main/java/com/example/rssfeed/Model/RSSObject.kt new file mode 100644 index 0000000..ee4b498 --- /dev/null +++ b/Dissertation/rsspage/src/main/java/com/example/rssfeed/Model/RSSObject.kt @@ -0,0 +1,7 @@ +package com.example.rssfeed.Model + +data class RSSObject( + val status: String, + val feed: Feed, + val items: List +) \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/res/drawable-anydpi/ic_refresh.xml b/Dissertation/rsspage/src/main/res/drawable-anydpi/ic_refresh.xml new file mode 100644 index 0000000..bcc9b72 --- /dev/null +++ b/Dissertation/rsspage/src/main/res/drawable-anydpi/ic_refresh.xml @@ -0,0 +1,11 @@ + + + diff --git a/Dissertation/rsspage/src/main/res/drawable-hdpi/ic_refresh.png b/Dissertation/rsspage/src/main/res/drawable-hdpi/ic_refresh.png new file mode 100644 index 0000000..8c851be Binary files /dev/null and b/Dissertation/rsspage/src/main/res/drawable-hdpi/ic_refresh.png differ diff --git a/Dissertation/rsspage/src/main/res/drawable-mdpi/ic_refresh.png b/Dissertation/rsspage/src/main/res/drawable-mdpi/ic_refresh.png new file mode 100644 index 0000000..5152663 Binary files /dev/null and b/Dissertation/rsspage/src/main/res/drawable-mdpi/ic_refresh.png differ diff --git a/Dissertation/rsspage/src/main/res/drawable-v24/ic_launcher_foreground.xml b/Dissertation/rsspage/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/Dissertation/rsspage/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/res/drawable-xhdpi/ic_refresh.png b/Dissertation/rsspage/src/main/res/drawable-xhdpi/ic_refresh.png new file mode 100644 index 0000000..3e8fa51 Binary files /dev/null and b/Dissertation/rsspage/src/main/res/drawable-xhdpi/ic_refresh.png differ diff --git a/Dissertation/rsspage/src/main/res/drawable-xxhdpi/ic_refresh.png b/Dissertation/rsspage/src/main/res/drawable-xxhdpi/ic_refresh.png new file mode 100644 index 0000000..b5df3b1 Binary files /dev/null and b/Dissertation/rsspage/src/main/res/drawable-xxhdpi/ic_refresh.png differ diff --git a/Dissertation/rsspage/src/main/res/drawable/gradient_background.xml b/Dissertation/rsspage/src/main/res/drawable/gradient_background.xml new file mode 100644 index 0000000..4b3ee7d --- /dev/null +++ b/Dissertation/rsspage/src/main/res/drawable/gradient_background.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/Dissertation/rsspage/src/main/res/drawable/ic_launcher_background.xml b/Dissertation/rsspage/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/Dissertation/rsspage/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Dissertation/rsspage/src/main/res/drawable/powerof10.png b/Dissertation/rsspage/src/main/res/drawable/powerof10.png new file mode 100644 index 0000000..464042e Binary files /dev/null and b/Dissertation/rsspage/src/main/res/drawable/powerof10.png differ diff --git a/Dissertation/rsspage/src/main/res/layout/activity_main.xml b/Dissertation/rsspage/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..36c9206 --- /dev/null +++ b/Dissertation/rsspage/src/main/res/layout/activity_main.xml @@ -0,0 +1,33 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/res/layout/row.xml b/Dissertation/rsspage/src/main/res/layout/row.xml new file mode 100644 index 0000000..4ac9c2a --- /dev/null +++ b/Dissertation/rsspage/src/main/res/layout/row.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/Dissertation/rsspage/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/Dissertation/rsspage/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/Dissertation/rsspage/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/Dissertation/rsspage/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/res/mipmap-hdpi/ic_launcher.webp b/Dissertation/rsspage/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/Dissertation/rsspage/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/Dissertation/rsspage/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/Dissertation/rsspage/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/Dissertation/rsspage/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/Dissertation/rsspage/src/main/res/mipmap-mdpi/ic_launcher.webp b/Dissertation/rsspage/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/Dissertation/rsspage/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/Dissertation/rsspage/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/Dissertation/rsspage/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/Dissertation/rsspage/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/Dissertation/rsspage/src/main/res/mipmap-xhdpi/ic_launcher.webp b/Dissertation/rsspage/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/Dissertation/rsspage/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/Dissertation/rsspage/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/Dissertation/rsspage/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/Dissertation/rsspage/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/Dissertation/rsspage/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/Dissertation/rsspage/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/Dissertation/rsspage/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/Dissertation/rsspage/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/Dissertation/rsspage/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/Dissertation/rsspage/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/Dissertation/rsspage/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/Dissertation/rsspage/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/Dissertation/rsspage/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/Dissertation/rsspage/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/Dissertation/rsspage/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/Dissertation/rsspage/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/Dissertation/rsspage/src/main/res/values-night/themes.xml b/Dissertation/rsspage/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..a938404 --- /dev/null +++ b/Dissertation/rsspage/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/res/values/colors.xml b/Dissertation/rsspage/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/Dissertation/rsspage/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/res/values/strings.xml b/Dissertation/rsspage/src/main/res/values/strings.xml new file mode 100644 index 0000000..c257d1d --- /dev/null +++ b/Dissertation/rsspage/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Rssfeed + \ No newline at end of file diff --git a/Dissertation/rsspage/src/main/res/values/themes.xml b/Dissertation/rsspage/src/main/res/values/themes.xml new file mode 100644 index 0000000..1554fbf --- /dev/null +++ b/Dissertation/rsspage/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/Dissertation/rsspage/src/test/java/com/example/rssfeed/ExampleUnitTest.kt b/Dissertation/rsspage/src/test/java/com/example/rssfeed/ExampleUnitTest.kt new file mode 100644 index 0000000..0e023a0 --- /dev/null +++ b/Dissertation/rsspage/src/test/java/com/example/rssfeed/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.example.rssfeed + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/Dissertation/settings.gradle b/Dissertation/settings.gradle new file mode 100644 index 0000000..7159616 --- /dev/null +++ b/Dissertation/settings.gradle @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + maven { url 'https://jitpack.io' } + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } + } +} +rootProject.name = "PowerOf10" +include ':app' diff --git a/PowerOf10/app/release/app-release.apk b/PowerOf10/app/release/app-release.apk new file mode 100644 index 0000000..eb29ef1 Binary files /dev/null and b/PowerOf10/app/release/app-release.apk differ diff --git a/PowerOf10/app/release/output-metadata.json b/PowerOf10/app/release/output-metadata.json new file mode 100644 index 0000000..b8614f5 --- /dev/null +++ b/PowerOf10/app/release/output-metadata.json @@ -0,0 +1,20 @@ +{ + "version": 3, + "artifactType": { + "type": "APK", + "kind": "Directory" + }, + "applicationId": "com.example.powerof10", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "versionCode": 1, + "versionName": "1.0", + "outputFile": "app-release.apk" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/PowerOf10/app/src/main/java/com/example/powerof10/ChartStyle.kt b/PowerOf10/app/src/main/java/com/example/powerof10/ChartStyle.kt new file mode 100644 index 0000000..e69de29